C++11 smart pointer 之 shared_ptr

  • Post author:
  • Post category:其他

std::shared_ptr

shared_ptr源码链接

定义于头文件  <memory>:
template<class T> class shared_ptr;

std::shared_ptr 是通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可占有同一对象。下列情况之一出现时销毁对象并解分配其内存:

  • 最后剩下的占有对象的 shared_ptr 被销毁;
  • 最后剩下的占有对象的 shared_ptr 被通过 operator= 或 reset() 赋值为另一指针。

用 delete 表达式或在构造期间提供给 shared_ptr 的定制删除器销毁对象。
shared_ptr 能在存储指向一个对象的指针时共享另一对象的所有权。此特性能用于在占有其所属对象时,指向成员对象。存储的指针为 get() 、解引用及比较运算符所访问。被管理指针是在 use_count 抵达零时传递给删除器者。

shared_ptr 亦可不占有对象,该情况下称它为空 (empty) (空 shared_ptr 可拥有非空存储指针,若以别名使用构造函数创建它)。

shared_ptr 的所有特化满足可复制构造 (CopyConstructible) 、可复制赋值 (CopyAssignable) 和可小于比较 (LessThanComparable) 的要求并可按语境转换为 bool 。

多个线程能在 shared_ptr 的不同实例上调用所有成员函数(包含复制构造函数与复制赋值)而不附加同步,即使这些实例是副本,且共享同一对象的所有权。若多个执行线程访问同一 shared_ptr 而不同步,且任一线程使用 shared_ptr 的非 const 成员函数,则将出现数据竞争;原子函数的 shared_ptr 特化能用于避免数据竞争。

成员类型

成员类型
element_type T (C++17 前) std::remove_extent_t (C++17 起)
weak_type (C++17 起) std::weak_ptr

成员函数

(构造函数): 构造新的 shared_ptr

(析构函数): 如果没有更多 shared_ptr 指向持有的对象,则析构对象

operator=: 对 shared_ptr 赋值

Modifiers

reset(): 替换所管理的对象
swap(): 交换所管理的对象

Observers

get(): 返回存储的指针

operator*(operator->): 解引用存储的指针

operator[]: 提供到被存储数组的带下标访问 (C++17)

use_count():返回 shared_ptr 所指对象的引用计数

unique()(C++20 前):检查所管理对象是否仅由当前shared_ptr 的实例管理

operator bool: 检查是否有关联的管理对象

owner_before(): 提供基于拥有者的共享指针排序

非成员函数

创建管理一个新对象的共享指针

make_shared()
make_shared_for_overwrite()

创建管理一个用分配器分配的新对象的共享指针

allocate_shared()
allocate_shared_for_overwrite()

应用 static_cast、dynamic_cast、const_cast 或 reinterpret_cast 到被存储指针

static_pointer_cast
dynamic_pointer_cast
const_pointer_cast
reinterpret_pointer_cast

返回指定类型中的删除器,若其拥有

get_deleter

与另一个 shared_ptr 或 nullptr 进行比较

operator==
operator!=
operator<
operator<=
operator>
operator>=
operator<=>

将存储的指针的值输出到输出流

operator<<

特化 std::swap 算法

std::swap(std::shared_ptr)

特化的原子操作 (C++20 中弃用)

std::atomic_is_lock_free(std::shared_ptr)
std::atomic_load(std::shared_ptr)
std::atomic_load_explicit(std::shared_ptr)
std::atomic_store(std::shared_ptr)
std::atomic_store_explicit(std::shared_ptr)
std::atomic_exchange(std::shared_ptr)
std::atomic_exchange_explicit(std::shared_ptr)
std::atomic_compare_exchange_weak(std::shared_ptr)
std::atomic_compare_exchange_strong(std::shared_ptr)
std::atomic_compare_exchange_weak_explicit(std::shared_ptr)
std::atomic_compare_exchange_strong_explicit(std::shared_ptr)

辅助类

std::hashstd::shared_ptr (C++11)std::shared_ptr 的散列支持 (类模板特化)

std::atomicstd::shared_ptr(C++20)原子共享指针 (类模板特化)

注意

只能通过复制构造或复制赋值其值给另一 shared_ptr ,将对象所有权与另一 shared_ptr 共享。用另一 shared_ptr 所占有的底层指针创建新的 shared_ptr 导致未定义行为。

std::shared_ptr 可以用于不完整类型 T 。然而,参数为裸指针的构造函数( template shared_ptr(Y*) )和 template void reset(Y*) 成员函数只可以用指向完整类型的指针调用(注意 std::unique_ptr 可以从指向不完整类型的裸指针构造)。

void del(void(*)()) {}
void fun() {}
int main(){
  std::shared_ptr<void()> ee(fun, del);
  (*ee)();
}

实现说明

在典型的实现中, std::shared_ptr 只保有二个指针:

  • get() 所返回的指针
  • 指向控制块的指针

控制块是一个动态分配的对象,其中包含:

  • 指向被管理对象的指针或被管理对象本身
  • 删除器(类型擦除)
  • 分配器(类型擦除)
  • 占有被管理对象的 shared_ptr 的数量
  • 涉及被管理对象的 weak_ptr 的数量

当调用 std::make_shared 或 std::allocate_shared 创建 shared_ptr 时,以单次分配创建控制块和被管理对象。被管理对象在控制块的数据成员中原位构造。通过 shared_ptr 构造函数之一创建 shared_ptr 时,被管理对象和控制块必须分离分配。此情况中,控制块存储指向被管理对象的指针。

shared_ptr 持有的指针是通过 get() 返回的;而控制块所持有的指针/对象则是最终引用计数归零时会被删除的那个。两者并不一定相等。

shared_ptr 的析构函数会将控制块中的 shared_ptr 计数器减一,如果减至零,控制块就会调用被管理对象的析构函数。但控制块本身直到 std::weak_ptr 计数器同样归零时才会释放。

既存实现中,若有共享指针指向同一控制块,则自增弱指针计数 。

为满足线程安全要求,引用计数器典型地用等价于用 std::memory_order_relaxed 的 std::atomic::fetch_add 自增(自减要求更强的顺序,以安全销毁控制块)。

示例

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
 
struct Base
{
    Base() { std::cout << "  Base::Base()\n"; }
    // 注意:此处非虚析构函数 OK
    ~Base() { std::cout << "  Base::~Base()\n"; }
};
 
struct Derived: public Base
{
    Derived() { std::cout << "  Derived::Derived()\n"; }
    ~Derived() { std::cout << "  Derived::~Derived()\n"; }
};
 
void thr(std::shared_ptr<Base> p)
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::shared_ptr<Base> lp = p; // 线程安全,虽然自增共享的 use_count
    {
        static std::mutex io_mutex;
        std::lock_guard<std::mutex> lk(io_mutex);
        std::cout << "local pointer in a thread:\n"
                  << "  lp.get() = " << lp.get()
                  << ", lp.use_count() = " << lp.use_count() << '\n';
    }
}
 


int main(int argc, char** argv)
{
    std::shared_ptr<Base> p = std::make_shared<Derived>();
 
    std::cout << "Created a shared Derived (as a pointer to Base)\n"
              << "  p.get() = " << p.get()
              << ", p.use_count() = " << p.use_count() << '\n';
    std::thread t1(thr, p), t2(thr, p), t3(thr, p);
    p.reset(); // 从 main 释放所有权
    std::cout << "Shared ownership between 3 threads and released\n"
              << "ownership from main:\n"
              << "  p.get() = " << p.get()
              << ", p.use_count() = " << p.use_count() << '\n';
    t1.join(); t2.join(); t3.join();
    std::cout << "All threads completed, the last one deleted Derived\n";
    return 0;
}

可能的输出:

Base::Base()
  Derived::Derived()
Created a shared Derived (as a pointer to Base)
  p.get() = 0x2299b30, p.use_count() = 1
Shared ownership between 3 threads and released
ownership from main:
  p.get() = 0, p.use_count() = 0
local pointer in a thread:
  lp.get() = 0x2299b30, lp.use_count() = 5
local pointer in a thread:
  lp.get() = 0x2299b30, lp.use_count() = 3
local pointer in a thread:
  lp.get() = 0x2299b30, lp.use_count() = 2
  Derived::~Derived()
  Base::~Base()
All threads completed, the last one deleted Derived

Tips

一、shared_ptr 作为值传递时,因为函数对实参进行拷贝,导致引用计数+1,作为引用传递时则不会:

#include <iostream>
#include <memory>

using namespace std;

void alias_pass(const shared_ptr<int> &p)  //必须const,否则编译不过
{
	cout << "alias pass count:"  << p.use_count << endl;
}

void value_pass(shared_ptr<int> p)
{
   cout << "value pass count:"  << p.use_count << endl;
}


int main(int argc, char **argv)
{
	shared_ptr<int> p = make_shared<int>(1);
	cout << "initial count:" << p.use_count() << endl;
	alias_pass(p);
	value_pass(p);
        return 0;
}

输出为:

initial count:1
alias pass count:1
value pass count:2

二、shared_ptr指向数组等非常规对象时,在其删除时可能会出现内存泄露等情况:

#include <iostream>
#include <memory>

using namespace std;

class DefaultDeleter {
public:
    DefaultDeleter() { cout << "Consturctor" << endl; }
    ~DefaultDeleter() { cout << "Destructor" << endl; }
};

int main(int argc, char **argv) {
    {
        shared_ptr<DefaultDeleter> p(new DefaultDeleter[4]);
        p.reset();
    }

    return 0;
}

输出为:

Consturctor
Consturctor
Consturctor
Consturctor
Destructor
Segmentation fault (core dumped)

为此,shared_ptr提供了自定义删除器 deleter

将回调函数传递给 shared_ptr 的构造函数,该构造函数将从其析构函数中调用以进行删除

1. 普通函数

#include <iostream>
#include <memory>

using namespace std;

class Foo {
public:
    Foo() { cout << "Consturctor" << endl; }
    ~Foo() { cout << "Destructor" << endl; }
};

template <class T>
void deleterArray(T *p) {
    cout << "delete array" << endl;
    delete[] p;
}

int main(int argc, char **argv) {
    {
        shared_ptr<Foo> p(new Foo[4], deleterArray<Foo>);
        p.reset();
    }

    return 0;
}

输出为:

Consturctor
Consturctor
Consturctor
Consturctor
delete array
Destructor
Destructor
Destructor
Destructor

2. 仿函数

#include <iostream>
#include <memory>

using namespace std;

class Foo {
public:
    Foo() { cout << "Consturctor" << endl; }
    ~Foo() { cout << "Destructor" << endl; }
};

template <class T>
class functorArrayDeleter {
public:
    void operator()(T *p) {
        std::cout << "functor array deleter array called" << std::endl;
        delete[] p;
    }
};

int main(int argc, char **argv) {
    {
        shared_ptr<Foo> p(new Foo[4], functorArrayDeleter<Foo>());
        p.reset();
    }

    return 0;
}

输出为:

Consturctor
Consturctor
Consturctor
Consturctor
functor array deleter array called
Destructor
Destructor
Destructor
Destructor

3. 使用Lambda表达式

std::shared_ptr<Foo> p(new Foo[5], [](Foo*t) {
	std::cout << "lambda array deleter function called" << std::endl;
	delete[] t;
});

4. std::default_delete

std::shared_ptr<Foo> p(new Foo[5], std::default_delete<Foo[]>());

5. 封装一个make_shared_array方法来让shared_ptr支持数组

template<typename T>
std::shared_ptr<T> make_shared_array(size_t size)
{
	return std::shared_ptr<T>(new T[size], std::default_delete <T[]>());
}

三、shared_ptr性能与线程安全

从上面的类图可以清楚的看出shared_ptr内部含有一个指向被管理对象(managed object)T的指针以及一个__shared_count对象,__shared_count对象包含一个指向管理对象(manager object)的基类指针,管理对象(manager object)由具有原子属性的use_count和weak_count、指向被管理对象(managed object)T的指针、以及用来销毁被管理对象的deleter组成,以下均将用new创建后托管给shared_ptr等智能指针管理的对象叫做被管理对象(managed object);shared_ptr等智能指针内部创建的用来维护被管理对象生命周期的实例叫做管理对象(manager object)。

由此可知,

  • shared_ptr 创建及删除时,都要对多个指针及内存进行操作,性能相比裸指针要差
  • shared_ptr 的count计数是原子操作,线程安全;但对managed object而言并非线程安全,多线程环境下,需要同步机制

四、尽量使用make_shared_ptr构造

  1. 不要使用同一个原始指针构造 shared_ptr,shared_ptr删除时,原指针失效(野指针)
  2. 不要用栈中的指针构造 shared_ptr 对象, 当 shared_ptr 对象超出作用域调用析构函数delete时会出错

五、 shared_ptr 相对于普通指针的优缺点

  • 缺少 ++, – – 和 [] 运算符

  • 相互引用时导致内存泄漏(使用weak_ptr解决)

class B;
class A
{
public:
	A()
	{
		std::cout << "A()" << std::endl;
	}
	~A()
	{
		std::cout << "~A()" << std::endl;
	}
public:
	Shared_Ptr<B> spa;
};
class B
{
public:
	B()
	{
		std::cout << "B()" << std::endl;
	}
	~B()
	{
		std::cout << "~B()" << std::endl;
	}
public:
	Shared_Ptr<A> spb;
};

int main()
{
	Shared_Ptr<A> pa(new A());
	Shared_Ptr<B> pb(new B());
	pa->spa = pb;
	pb->spb = pa;
	return 0;
}

输出为:

A()
B()

此时pa,pb并没有析构,造成内存泄露


版权声明:本文为qt_dog原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。