如何:创建和使用 shared_ptr 实例

shared_ptr 类型是 C++ 标准库中的一种智能指针,专为多个所有者需要管理对象生命周期的方案而设计。 在您初始化一个 shared_ptr 之后,您可复制它,按值将其传入函数参数,然后将其分配给其他 shared_ptr 实例。 所有实例均指向同一个对象,并共享对一个“控制块”(每当新的 shared_ptr 添加、超出范围或重置时增加和减少引用计数)的访问权限。 当引用计数达到零时,控制块将删除内存资源和自身。

下图显示了指向一个内存位置的几个 shared_ptr 实例。

显示指向一个内存位置的两个 shared_ptr 实例的示意图。

第一个示意图显示了一个共享指针 P1,它指向 MyClass 实例以及引用计数 = 1 的控制块。 第二个示意图显示添加了另一个共享指针 P2,它也指向 MyClass 实例和共享控制块,该控制块现在的引用计数为 2。

示例设置

以下示例均假设你已经包含所需标头并声明所需类型,如下所示:

// shared_ptr-examples.cpp
// The following examples assume these declarations:
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

struct MediaAsset
{
    virtual ~MediaAsset() = default; // make it polymorphic
};

struct Song : public MediaAsset
{
    std::wstring artist;
    std::wstring title;
    Song(const std::wstring& artist_, const std::wstring& title_) :
        artist{ artist_ }, title{ title_ } {}
};

struct Photo : public MediaAsset
{
    std::wstring date;
    std::wstring location;
    std::wstring subject;
    Photo(
        const std::wstring& date_,
        const std::wstring& location_,
        const std::wstring& subject_) :
        date{ date_ }, location{ location_ }, subject{ subject_ } {}
};

using namespace std;

int main()
{
    // The examples go here, in order:
    // Example 1
    // Example 2
    // Example 3
    // Example 4
    // Example 6
}

示例 1

如有可能,第一次创建内存资源时,请使用 make_shared 函数创建 shared_ptrmake_shared 异常安全。 它使用同一调用为控制块和资源分配内存,这会减少构造开销。 如果不使用 make_shared,则必须先使用显式 new 表达式来创建对象,然后才能将其传递到 shared_ptr 构造函数。 以下示例演示了同时声明和初始化 shared_ptr 和新对象的各种方式。


// Use make_shared function when possible.
auto sp1 = make_shared<Song>(L"The Beatles", L"Im Happy Just to Dance With You");

// Ok, but slightly less efficient. 
// Note: Using new expression as constructor argument
// creates no named variable for other code to access.
shared_ptr<Song> sp2(new Song(L"Lady Gaga", L"Just Dance"));

// When initialization must be separate from declaration, e.g. class members, 
// initialize with nullptr to make your programming intent explicit.
shared_ptr<Song> sp5(nullptr);
//Equivalent to: shared_ptr<Song> sp5;
//...
sp5 = make_shared<Song>(L"Elton John", L"I'm Still Standing");

示例 2

以下示例演示如何声明和初始化对由其他 shared_ptr 分配的对象具有共享所有权的 shared_ptr 实例。 假设 sp2 是已初始化的 shared_ptr

//Initialize with copy constructor. Increments ref count.
auto sp3(sp2);

//Initialize via assignment. Increments ref count.
auto sp4 = sp2;

//Initialize with nullptr. sp7 is empty.
shared_ptr<Song> sp7(nullptr);

// Initialize with another shared_ptr. sp1 and sp2
// swap pointers as well as ref counts.
sp1.swap(sp2);

示例 3

在你使用复制元素的算法时,shared_ptr 在 C++ 标准库容器中很有用。 您可以将元素包装在 shared_ptr 中,然后将其复制到其他容器中(请记住,只要您需要,基础内存就会一直有效)。 以下示例演示如何在向量中对 remove_copy_if 实例使用 shared_ptr 算法。

vector<shared_ptr<Song>> v {
  make_shared<Song>(L"Bob Dylan", L"The Times They Are A Changing"),
  make_shared<Song>(L"Aretha Franklin", L"Bridge Over Troubled Water"),
  make_shared<Song>(L"Thalía", L"Entre El Mar y Una Estrella")
};

vector<shared_ptr<Song>> v2;
remove_copy_if(v.begin(), v.end(), back_inserter(v2), [] (shared_ptr<Song> s) 
{
    return s->artist.compare(L"Bob Dylan") == 0;
});

for (const auto& s : v2)
{
    wcout << s->artist << L":" << s->title << endl;
}

示例 4

您可以使用 dynamic_pointer_caststatic_pointer_castconst_pointer_cast 来转换 shared_ptr。 这些函数类似于 dynamic_caststatic_castconst_cast 运算符。 以下示例演示如何测试基类的 shared_ptr 向量中每个元素的派生类型,然后复制元素并显示有关它们的信息。

vector<shared_ptr<MediaAsset>> assets {
  make_shared<Song>(L"Himesh Reshammiya", L"Tera Surroor"),
  make_shared<Song>(L"Penaz Masani", L"Tu Dil De De"),
  make_shared<Photo>(L"2011-04-06", L"Redmond, WA", L"Soccer field at Microsoft.")
};

vector<shared_ptr<MediaAsset>> photos;

copy_if(assets.begin(), assets.end(), back_inserter(photos), [] (shared_ptr<MediaAsset> p) -> bool
{
    // Use dynamic_pointer_cast to test whether
    // element is a shared_ptr<Photo>.
    shared_ptr<Photo> temp = dynamic_pointer_cast<Photo>(p);
    return temp.get() != nullptr;
});

for (const auto&  p : photos)
{
    // We know that the photos vector contains only 
    // shared_ptr<Photo> objects, so use static_cast.
    wcout << "Photo location: " << (static_pointer_cast<Photo>(p))->location << endl;
}

示例 5

您可以通过下列方式将 shared_ptr 传递给其他函数:

  • 按值传递 shared_ptr。 这将调用复制构造函数,增加引用计数,并使被调用方成为所有者。 此操作的开销很小,但此操作的开销可能很大,具体取决于要传递多少 shared_ptr 对象。 当调用方和被调用方之间的(隐式或显式)代码协定要求被调用方是所有者时,请使用此选项。

  • 按引用或常量引用传递 shared_ptr。 在这种情况下,引用计数不会增加,并且只要调用方不超出范围,被调用方就可以访问指针。 或者,被调用方可以决定基于引用创建一个 shared_ptr,并成为一个共享所有者。 当调用方并不知道被调用方,或当您由于性能原因必须传递一个 shared_ptr 且希望避免复制操作时,请使用此选项。

  • 传递基础指针或对基础对象的引用。 这使被调用方能够使用对象,但不会使其能够共享所有权或延长生存期。 如果被调用方通过原始指针创建一个 shared_ptr,则新的 shared_ptr 独立于原始的,并且不会控制基础资源。 当调用方和被调用方之间的协定明确指定调用方保留 shared_ptr 生存期的所有权时,请使用此选项。

  • 在确定如何传递 shared_ptr 时,确定被调用方是否必须共享基础资源的所有权。 “所有者”是只要它需要就可以使基础资源一直有效的对象或函数。 如果调用方必须保证被调用方可以将指针的生命延长到其(函数)生存期以外,则请使用第一个选项。 如果您不关心被调用方是否延长生存期,则按引用传递并让被调用方复制或不复制它。

  • 如果必须为帮助程序函数提供对基础指针的访问权限,并且你知道帮助程序函数将使用该指针并且将在调用函数返回前返回,则该函数不必共享基础指针的所有权。 它只需在调用方的 shared_ptr 的生存期内访问指针即可。 在这种情况下,按引用传递 shared_ptr 或传递原始指针或对基础对象的引用是安全的。 通过此方式传递将提供一个小的性能好处,并且还有助于您表达编程意图。

  • 有时,例如,在一个 std::vector<shared_ptr<T>> 中,您可能必须将每个 shared_ptr 传递给 lambda 表达式主体或命名函数对象。 如果 lambda 或函数没有存储指针,则将按引用传递 shared_ptr 以避免调用每个元素的复制构造函数。

void use_shared_ptr_by_value(shared_ptr<int> sp);

void use_shared_ptr_by_reference(shared_ptr<int>& sp);
void use_shared_ptr_by_const_reference(const shared_ptr<int>& sp);

void use_raw_pointer(int* p);
void use_reference(int& r);

void test() {
    auto sp = make_shared<int>(5);

    // Pass the shared_ptr by value.
    // This invokes the copy constructor, increments the reference count, and makes the callee an owner.
    use_shared_ptr_by_value(sp);

    // Pass the shared_ptr by reference or const reference.
    // In this case, the reference count isn't incremented.
    use_shared_ptr_by_reference(sp);
    use_shared_ptr_by_const_reference(sp);

    // Pass the underlying pointer or a reference to the underlying object.
    use_raw_pointer(sp.get());
    use_reference(*sp);

    // Pass the shared_ptr by value.
    // This invokes the move constructor, which doesn't increment the reference count
    // but in fact transfers ownership to the callee.
    use_shared_ptr_by_value(move(sp));
}

示例 6

以下示例演示 shared_ptr 如何重载各种比较运算符以在 shared_ptr 实例所有的内存上实现指针比较。


// Initialize two separate raw pointers.
// Note that they contain the same values.
auto song1 = new Song(L"Village People", L"YMCA");
auto song2 = new Song(L"Village People", L"YMCA");

// Create two unrelated shared_ptrs.
shared_ptr<Song> p1(song1);    
shared_ptr<Song> p2(song2);

// Unrelated shared_ptrs are never equal.
wcout << "p1 < p2 = " << std::boolalpha << (p1 < p2) << endl;
wcout << "p1 == p2 = " << std::boolalpha <<(p1 == p2) << endl;

// Related shared_ptr instances are always equal.
shared_ptr<Song> p3(p2);
wcout << "p3 == p2 = " << std::boolalpha << (p3 == p2) << endl; 

另请参阅

智能指针(现代 C++)