unique_ptr
C++11引入了std::unique_ptr
主要是为了提供一种在资源管理方面更为安全和高效的替代方案。std::unique_ptr
是一种独占所有权的智能指针,它确保在程序的生命周期内只有一个指针可以拥有和管理某个资源,通常是动态分配的对象或数组。
std::unique_ptr
特点:
-
独占所有权:
std::unique_ptr
不允许多个指针共享同一个资源,这意味着在任意时刻只有一个unique_ptr
可以拥有资源。这有助于防止资源泄漏和难以调试的所有权问题。 -
自动资源释放: 当
std::unique_ptr
的生命周期结束时(例如,离开其作用域),它会自动释放所拥有的资源,无需手动调用delete
或delete[]
。这有助于避免内存泄漏,并简化了资源管理。 -
移动语义:
std::unique_ptr
支持移动语义,允许资源的高效转移。这使得在函数之间传递std::unique_ptr
更为高效,避免了不必要的资源复制。 -
定制删除器: 可以通过指定删除器(deleter)来自定义资源的释放行为。这使得
std::unique_ptr
非常灵活,能够管理各种类型的资源。 -
NULL指针检查:
std::unique_ptr
提供了对空指针的检查,可以帮助避免潜在的运行时错误。
成员函数及其说明:
-
构造函数:
std::unique_ptr<T>
:默认构造函数,创建一个空的std::unique_ptr
。std::unique_ptr<T>(T* ptr)
:接受一个原始指针并拥有它的所有权。std::unique_ptr<T, Deleter>(T* ptr, Deleter d)
:使用指定的删除器创建std::unique_ptr
。
-
无法进行复制构造和赋值操作,支持移动语义:
std::unique_ptr<T>(std::unique_ptr<T>&& other)
:移动构造函数,允许将资源的所有权从一个std::unique_ptr
转移到另一个。std::unique_ptr<T>& operator=(std::unique_ptr<T>&& other)
:移动赋值操作符。
-
释放资源:
T* release()
:释放对资源的所有权,并返回原始指针。void reset()
:释放当前指针拥有的资源,并将指针置为空。void reset(T* ptr)
:释放当前指针拥有的资源,并接管参数指针的所有权。
-
获取指针信息:
T* get()
:返回原始指针。T& operator*()
:解引用操作符,返回指向的对象的引用。T* operator->()
:成员访问操作符,用于通过std::unique_ptr
访问对象的成员。
-
检查空指针:
bool operator==(std::nullptr_t) const noexcept
:检查是否为 nullptr。explicit operator bool() const noexcept
:将std::unique_ptr
转换为布尔值,检查是否拥有资源。
-
自定义删除器:
template <class Deleter> std::unique_ptr(T* ptr, Deleter d)
:使用自定义删除器创建std::unique_ptr
。template <class Deleter> Deleter& get_deleter() noexcept
:获取当前删除器。
使用示例
- 创建unique_ptr
c
// 构造方式
std::unique_ptr<int> p1();
std::unique_ptr<int> p2(nullptr);
- 无法进行复制构造和赋值操作
c
std::unique_ptr<int> p3(new int);
std::unique_ptr<int> p4(p3);
std::unique_ptr<int> p5(p3);
- 支持移动语义
c
std::unique_ptr<int> p6(new int);
std::unique_ptr<int> p7(std::move(p6));
- 释放当前资源,设置为空指针
c
std::unique_ptr<int> p8(new int);
std::cout << "before address: " << p8.get() << std::endl;
p8.reset();
std::cout << "after address: " << p8.get() << std::endl;
输出
c
before address: 0x2588030
after address: 0
- 解引用
c
std::unique_ptr<int> p9(new int(6));
std::cout << "*p9 = " << *p9 << std::endl;
输出
c
*p9 = 6
- 释放资源的所有权,返回原始指针的引用
c
std::unique_ptr<int> p10(new int(10));
int *p11 = p10.release();
std::cout << "*p11 = " << *p11 << std::endl;
shared_ptr
c
每个shared_ptr都有一个关联的计数值,通常称为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。
例如,当用一个shared_ptr初始化另一个shred_ptr,或将它当做参数传递给一个函数以及作为函数的返回值时,它
所关联的计数器就会递增。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的
shared_ptr离开其作用域)时,计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理
的对象。
成员函数线程安全,非成员函数线程不安全。成员函数线程安全通过std::atomic::fetch_add with std::memory_order_relaxed 来实现。
std::shared_ptr
std::shared_ptr特点:
-
共享所有权:
std::shared_ptr
允许多个智能指针共享同一个资源,通过引用计数来追踪资源的所有权。当最后一个指向资源的std::shared_ptr
被销毁或重置时,资源才会被释放。 -
引用计数:
std::shared_ptr
内部维护了一个引用计数,用于记录有多少个智能指针共享同一个资源。引用计数通过原子操作进行增加和减少,从而确保在多线程环境下的安全性。 -
弱引用支持:
std::shared_ptr
配合std::weak_ptr
提供了弱引用的支持,可以用于解决循环引用的问题,避免内存泄漏。 -
自动释放资源: 当最后一个拥有资源的
std::shared_ptr
被销毁时,它会自动释放所拥有的资源,无需手动调用delete
。 -
空指针检查: 提供了安全的空指针检查,避免了访问空指针的运行时错误。
主要成员函数
-
构造函数:
std::shared_ptr<T>()
:默认构造函数,创建一个空的std::shared_ptr
。std::shared_ptr<T>(T* ptr)
:接受一个原始指针并拥有它的所有权。std::shared_ptr<T>(const std::shared_ptr<T>& other)
:复制构造函数,共享资源。std::shared_ptr<T>(std::nullptr_t)
:创建一个空的std::shared_ptr
。
-
引用计数相关:
long use_count() const noexcept
:返回当前共享资源的引用计数。bool unique() const noexcept
:检查是否是唯一拥有资源的std::shared_ptr
。
-
移动语义:
std::shared_ptr<T>(std::shared_ptr<T>&& other)
:移动构造函数,将资源的所有权从一个std::shared_ptr
转移到另一个。
-
赋值操作符:
std::shared_ptr<T>& operator=(const std::shared_ptr<T>& other)
:复制赋值操作符,共享资源。std::shared_ptr<T>& operator=(std::shared_ptr<T>&& other)
:移动赋值操作符。
-
获取指针信息:
T* get()
:返回原始指针。T& operator*()
:解引用操作符,返回指向的对象的引用。T* operator->()
:成员访问操作符,用于通过std::shared_ptr
访问对象的成员。
-
空指针检查:
bool operator==(std::nullptr_t) const noexcept
:检查是否为 nullptr。explicit operator bool() const noexcept
:将std::shared_ptr
转换为布尔值,检查是否拥有资源。
-
自定义删除器:
template <class Deleter> std::shared_ptr(T* ptr, Deleter d)
:使用自定义删除器创建std::shared_ptr
。template <class Deleter> Deleter& get_deleter() noexcept
:获取当前删除器。
创建实例
通过make_shared 或者 new 来构造实例
c
shared_ptr<int> p1 = make_shared<int>(12345);
cout << "p1: " << *p1 << endl;
shared_ptr<int> p2(new int(23456));
cout << "p2: " << *p2 << endl;
new 和 make_shared的区别: new方法在堆上创建了两块内存:1:存储int
。2:控制块上用于引用计数的内存,管理附加此内存的 shared_ptr 对象的计数,最初计数将为1。 std::make_shared 一次性为int
对象和用于引用计数的数据都分配了内存,而new
操作符只是为int
分配了内存。
性能差异:
- 性能:
std::make_shared
的性能可能更好,因为它可以一次性分配内存来存储对象和控制块,减少了额外的动态内存分配。 - 异常安全性:
std::make_shared
在分配内存时提供了强烈的异常安全性,因为它只有在成功分配了所有内存时才会构造对象。而直接使用构造函数可能在分配内存后抛出异常,导致资源泄漏。
- 通过拷贝构造初始化,会增加引用计数值
c
shared_ptr<int> p2(new int(23456));
cout << "p2: " << *p2 << endl;
shared_ptr<int> p3(p2);
cout << "p2: " << p2.use_count() << endl;
cout << "p3: " << p3.use_count() << endl;
- 当share_ptr实例p和实例q,指向相同的类型,通过赋值操作 p = q ,会减少 p 的引用计数, 增加 q 的引用计数。
c
class Example
{
public:
Example(const char *name) : e(1) { cout << "Example Constructor..." << name << endl; }
~Example() { cout << "Example Destructor..." << endl; }
int e;
};
int main() {
shared_ptr<Example> p4(new Example("a"));
shared_ptr<Example> p5(new Example("b"));
cout << "p4: " << p4.use_count() << endl;
cout << "p5: " << p5.use_count() << endl;
p4 = p5;
cout << "p4: " << p4.use_count() << endl;
cout << "p5: " << p5.use_count() << endl;
}
输出:
c
Example Constructor...a
Example Constructor...b
p4: 1
p5: 1
Example Destructor...
p4: 2
p5: 2
Example Destructor...
- 检查引用计数 调用use_count返回引用计数值
c
shared_ptr<int> p3(p2);
cout << "p2: " << p2.use_count() << endl;
cout << "p3: " << p3.use_count() << endl;