shared_ptr 类型是 C++ 标准库中的一种智能指针,专为多个所有者可能必须管理内存中对象的生命周期的情况而设计。初始化 shared_ptr 后,您可以复制它,在函数参数中按值传递它,并将其分配给其他 shared_ptr 实例。所有实例都指向同一个对象,并共享对一个“控制块”的访问,每当添加新的 shared_ptr、超出范围或重置时,该“控制块”都会增加和减少引用计数。当引用计数达到零时,控制块会删除内存资源和自身。
下图显示了指向一个内存位置的多个 shared_ptr 实例。
下面的所有示例都假定您已经包含了所需的标头并声明了所需的类型,如下所示:
// shared_ptr-examples.cpp
// The following examples assume these declarations:
#include
#include
#include
#include
#include
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
}
在第一次创建内存资源时,尽可能使用 make_shared 函数创建一个 shared_ptr。 make_shared 是异常安全的。它使用相同的调用为控制块和资源分配内存,减少了构建开销。如果不使用 make_shared,则必须在将对象传递给 shared_ptr 构造函数之前使用显式 new 表达式来创建对象。以下示例显示了声明和初始化 shared_ptr 以及新对象的各种方法。
// Use make_shared function when possible.
auto sp1 = make_shared(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 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 sp5(nullptr);
//Equivalent to: shared_ptr sp5;
//...
sp5 = make_shared(L"Elton John", L"I'm Still Standing");
以下示例显示了如何声明和初始化 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 sp7(nullptr);
// Initialize with another shared_ptr. sp1 and sp2
// swap pointers as well as ref counts.
sp1.swap(sp2);
当您使用复制元素的算法时,shared_ptr 在 C++ 标准库容器中也很有帮助。您可以将元素包装在 shared_ptr 中,然后将其复制到其他容器中,并了解底层内存在您需要时有效,并且不再有效。以下示例显示如何在向量中的 shared_ptr 实例上使用 remove_copy_if 算法。
vector> v {
make_shared(L"Bob Dylan", L"The Times They Are A Changing"),
make_shared(L"Aretha Franklin", L"Bridge Over Troubled Water"),
make_shared(L"Thalía", L"Entre El Mar y Una Estrella")
};
vector> v2;
remove_copy_if(v.begin(), v.end(), back_inserter(v2), [] (shared_ptr s)
{
return s->artist.compare(L"Bob Dylan") == 0;
});
for (const auto& s : v2)
{
wcout << s->artist << L":" << s->title << endl;
}
您可以使用 dynamic_pointer_cast、static_pointer_cast 和 const_pointer_cast 来转换 shared_ptr。这些函数类似于 dynamic_cast、static_cast 和 const_cast 运算符。下面的例子展示了如何测试基类的shared_ptr向量中每个元素的派生类型,然后复制元素并显示它们的信息。
vector> assets {
make_shared(L"Himesh Reshammiya", L"Tera Surroor"),
make_shared(L"Penaz Masani", L"Tu Dil De De"),
make_shared(L"2011-04-06", L"Redmond, WA", L"Soccer field at Microsoft.")
};
vector> photos;
copy_if(assets.begin(), assets.end(), back_inserter(photos), [] (shared_ptr p) -> bool
{
// Use dynamic_pointer_cast to test whether
// element is a shared_ptr.
shared_ptr temp = dynamic_pointer_cast(p);
return temp.get() != nullptr;
});
for (const auto& p : photos)
{
// We know that the photos vector contains only
// shared_ptr objects, so use static_cast.
wcout << "Photo location: " << (static_pointer_cast(p))->location << endl;
}
您可以通过以下方式将 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 如何重载各种比较运算符以在 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 p1(song1);
shared_ptr 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 p3(p2);
wcout << "p3 == p2 = " << std::boolalpha << (p3 == p2) << endl;