C++ unique_ptr、shared_ptr、weak_ptr详解(包含代码示例)
一、std::unique_ptr详解
std::unique_ptr
是C++11标准引入的智能指针,它是一个独占所有权的智能指针,这意味着同一时间只能有一个unique_ptr
拥有某个对象的所有权。它在对象生命周期管理上提供了安全和高效的方式,避免了内存泄漏和未定义行为。
1. std::unique_ptr
的详细介绍
-
定义和初始化:
std::unique_ptr<int> ptr1(new int(5)); // 使用裸指针初始化 std::unique_ptr<int> ptr2 = std::make_unique<int>(10); // 使用std::make_unique初始化
-
移动语义:
std::unique_ptr
不允许复制,但可以移动。这意味着可以将所有权从一个unique_ptr
转移到另一个。std::unique_ptr<int> ptr3 = std::move(ptr1); // ptr1的所有权转移给ptr3
-
释放资源:
当std::unique_ptr
被销毁时,它会自动释放所拥有的资源。ptr2.reset(); // 手动释放资源
2. 优点和缺点
优点:
- 自动资源管理: 避免了手动释放内存,防止内存泄漏。
- 独占所有权: 确保一个对象同时只有一个所有者,避免悬挂指针。
- 效率高: 没有额外的引用计数开销。
缺点:
- 不支持复制: 只能通过移动语义转移所有权,不能复制
unique_ptr
。 - 需要注意循环依赖: 如果两个对象互相持有
std::unique_ptr
,可能会导致无法正确释放资源。
3. 适用场景
- 独占资源管理: 适用于资源需要严格独占所有权的场景,如文件句柄、数据库连接等。
- 工厂函数返回值: 可以用来返回动态分配的对象而不担心内存泄漏。
- 容器中的元素:
std::unique_ptr
可以安全地存放在标准容器中,如std::vector
。
4. 代码示例及解释
#include <iostream>
#include <memory>
#include <vector>
// 一个简单的类,用于演示
class MyClass
{
public:
MyClass(int value) : value(value)
{
std::cout << "Constructor: " << value << std::endl;
}
~MyClass()
{
std::cout << "Destructor: " << value << std::endl;
}
void show() const
{
std::cout << "Value: " << value << std::endl;
}
private:
int value;
};
int main()
{
// 使用std::unique_ptr管理动态分配的MyClass对象
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(1);
ptr1->show();
// 转移所有权
std::unique_ptr<MyClass> ptr2 = std::move(ptr1);
if (!ptr1)
{
std::cout << "ptr1 is now null." << std::endl;
}
ptr2->show();
// std::unique_ptr在容器中的使用
std::vector<std::unique_ptr<MyClass>> vec;
vec.push_back(std::make_unique<MyClass>(2));
vec.push_back(std::make_unique<MyClass>(3));
for (const auto& ptr : vec)
{
ptr->show();
}
// 手动释放资源
vec.clear();
std::cout << "Vector cleared." << std::endl;
return 0;
}
代码运行结果
Constructor: 1
Value: 1
ptr1 is now null.
Value: 1
Constructor: 2
Constructor: 3
Value: 2
Value: 3
Vector cleared.
Destructor: 3
Destructor: 2
Destructor: 1
代码解释
-
创建和使用
std::unique_ptr
:std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(1);
创建一个
MyClass
对象,使用ptr1
管理其生命周期,并输出ptr1
的值。 -
转移所有权:
std::unique_ptr<MyClass> ptr2 = std::move(ptr1);
将
ptr1
的所有权转移给ptr2
,并检查ptr1
是否为空。 -
std::unique_ptr
在容器中的使用:std::vector<std::unique_ptr<MyClass>> vec; vec.push_back(std::make_unique<MyClass>(2)); vec.push_back(std::make_unique<MyClass>(3));
将多个
MyClass
对象的所有权存放在一个std::vector
中,并输出每个对象的值。 -
手动释放资源:
vec.clear();
清空容器,自动调用析构函数释放资源。
通过这个示例,可以看到std::unique_ptr
如何简化内存管理并确保资源的正确释放。
二、std::shared_ptr
详解
std::shared_ptr
是C++11标准引入的另一种智能指针,与std::unique_ptr
不同,std::shared_ptr
允许多个智能指针共享同一个对象。当最后一个std::shared_ptr
被销毁时,所管理的对象才会被释放。std::shared_ptr
使用引用计数来跟踪有多少智能指针指向同一对象。
1. std::shared_ptr
的详细介绍
-
定义和初始化:
std::shared_ptr<int> ptr1(new int(5)); // 使用裸指针初始化 std::shared_ptr<int> ptr2 = std::make_shared<int>(10); // 使用std::make_shared初始化
-
引用计数:
std::shared_ptr
内部维护一个引用计数,记录指向同一对象的shared_ptr
实例的数量。std::shared_ptr<int> ptr3 = ptr2; // 引用计数增加
-
释放资源:
当引用计数变为0时,std::shared_ptr
自动释放所管理的对象。ptr2.reset(); // ptr2不再指向对象,但对象不被释放,因为ptr3仍指向它
2. 优点和缺点
优点:
- 自动资源管理: 避免手动管理内存,防止内存泄漏。
- 共享所有权: 允许多个指针共享同一个对象,适用于需要共享所有权的场景。
- 安全: 引用计数机制确保对象在不再需要时自动释放,防止悬挂指针。
缺点:
- 性能开销: 引用计数增加了内存和时间开销,尤其是在多线程环境中需要原子操作。
- 循环引用问题: 两个对象互相持有
std::shared_ptr
可能导致内存泄漏。
3. 适用场景
- 共享所有权的资源管理: 适用于需要在多个地方共享同一资源的场景,如观察者模式、图结构等。
- 长生命周期对象: 当对象需要在多个模块中使用且生命周期难以预测时。
- 需要动态管理对象生命周期: 如动态插件系统中需要管理的对象。
4. 代码示例及解释
#include <iostream>
#include <memory>
#include <vector>
// 一个简单的类,用于演示
class MyClass
{
public:
MyClass(int value) : value(value)
{
std::cout << "Constructor: " << value << std::endl;
}
~MyClass()
{
std::cout << "Destructor: " << value << std::endl;
}
void show() const
{
std::cout << "Value: " << value << std::endl;
}
private:
int value;
};
void use_shared_ptr(std::shared_ptr<MyClass> ptr)
{
std::cout << "Inside function: ";
ptr->show();
}
int main()
{
// 使用std::shared_ptr管理动态分配的MyClass对象
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(1);
ptr1->show();
// 共享所有权
std::shared_ptr<MyClass> ptr2 = ptr1;
if (ptr1 && ptr2)
{
std::cout << "Both ptr1 and ptr2 are valid.\n";
}
// 引用计数
std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;
std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl;
// 传递shared_ptr给函数
use_shared_ptr(ptr2);
// std::shared_ptr在容器中的使用
std::vector<std::shared_ptr<MyClass>> vec;
vec.push_back(std::make_shared<MyClass>(2));
vec.push_back(std::make_shared<MyClass>(3));
for (const auto& ptr : vec)
{
ptr->show();
}
// 手动释放资源
ptr1.reset();
std::cout << "ptr1 reset, ptr2 use count: " << ptr2.use_count() << std::endl;
return 0;
}
代码运行结果
Constructor: 1
Value: 1
Both ptr1 and ptr2 are valid.
ptr1 use count: 2
ptr2 use count: 2
Inside function: Value: 1
Constructor: 2
Constructor: 3
Value: 2
Value: 3
ptr1 reset, ptr2 use count: 1
Destructor: 3
Destructor: 2
Destructor: 1
代码解释
-
创建和使用
std::shared_ptr
:std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(1);
创建一个
MyClass
对象,使用ptr1
管理其生命周期,并输出ptr1
的值。 -
共享所有权:
std::shared_ptr<MyClass> ptr2 = ptr1;
ptr1
和ptr2
共享同一个MyClass
对象,并输出引用计数。 -
传递
std::shared_ptr
给函数:use_shared_ptr(ptr2);
将
ptr2
传递给函数,函数内使用该指针访问对象。 -
std::shared_ptr
在容器中的使用:std::vector<std::shared_ptr<MyClass>> vec; vec.push_back(std::make_shared<MyClass>(2)); vec.push_back(std::make_shared<MyClass>(3));
将多个
MyClass
对象的所有权存放在一个std::vector
中,并输出每个对象的值。 -
手动释放资源:
ptr1.reset();
重置
ptr1
,但对象不被释放,因为ptr2
仍然持有该对象的所有权。
通过这个示例,可以看到std::shared_ptr
如何简化内存管理、支持共享所有权并确保资源的正确释放。
三、std::weak_ptr
详解
std::weak_ptr
是C++11标准引入的智能指针,专门用于解决std::shared_ptr
的循环引用问题。它不增加引用计数,而是提供了一种弱引用,用于观察和访问std::shared_ptr
所管理的对象,而不会影响其生命周期。
1. std::weak_ptr
的详细介绍
-
定义和初始化:
std::shared_ptr<int> sp = std::make_shared<int>(10); std::weak_ptr<int> wp = sp; // 使用shared_ptr初始化weak_ptr
-
访问对象:
std::weak_ptr
不能直接访问对象,需要通过std::shared_ptr
来访问。if (auto locked = wp.lock()) { // 返回shared_ptr,如果对象存在 std::cout << *locked << std::endl; } else { std::cout << "Object no longer exists." << std::endl; }
-
检查有效性:
可以使用expired()
检查对象是否被销毁。if (wp.expired()) { std::cout << "Object no longer exists." << std::endl; }
2. 优点和缺点
优点:
- 解决循环引用问题: 避免
std::shared_ptr
之间的循环引用导致内存泄漏。 - 轻量级: 不增加引用计数,对性能影响小。
- 安全访问: 通过
lock()
方法安全地访问对象。
缺点:
- 不能直接访问对象: 需要通过
std::shared_ptr
来访问,这增加了使用的复杂性。 - 检查有效性: 在访问前需要显式检查对象是否有效,稍显繁琐。
3. 适用场景
- 观察者模式: 适用于需要观测但不影响对象生命周期的场景。
- 避免循环引用: 如图结构、父子关系树等,防止对象间的循环引用。
- 缓存机制: 用于实现对象缓存,允许缓存项被外部持有而不延长其生命周期。
4. 代码示例及解释
#include <iostream>
#include <memory>
// 一个简单的类,用于演示
class MyClass
{
public:
MyClass(int value) : value(value)
{
std::cout << "Constructor: " << value << std::endl;
}
~MyClass()
{
std::cout << "Destructor: " << value << std::endl;
}
void show() const
{
std::cout << "Value: " << value << std::endl;
}
private:
int value;
};
void check_weak_ptr(const std::weak_ptr<MyClass>& wp)
{
if (auto sp = wp.lock())
{
std::cout << "Object is alive: ";
sp->show();
}
else
{
std::cout << "Object no longer exists." << std::endl;
}
}
int main()
{
std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>(1);
std::weak_ptr<MyClass> wp1 = sp1;
// 访问对象
check_weak_ptr(wp1);
// 共享所有权
std::shared_ptr<MyClass> sp2 = sp1;
std::cout << "sp1 use count: " << sp1.use_count() << std::endl;
// 重置shared_ptr,weak_ptr不影响对象的生命周期
sp1.reset();
std::cout << "sp1 reset, sp2 use count: " << sp2.use_count() << std::endl;
// 访问对象
check_weak_ptr(wp1);
// 释放所有shared_ptr
sp2.reset();
std::cout << "sp2 reset." << std::endl;
// 访问对象
check_weak_ptr(wp1);
return 0;
}
代码运行结果
Constructor: 1
Object is alive: Value: 1
sp1 use count: 2
sp1 reset, sp2 use count: 1
Object is alive: Value: 1
sp2 reset.
Object no longer exists.
Destructor: 1
代码解释
-
创建和使用
std::shared_ptr
和std::weak_ptr
:std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>(1); std::weak_ptr<MyClass> wp1 = sp1;
创建一个
MyClass
对象,并使用shared_ptr
管理其生命周期,同时创建一个weak_ptr
观察该对象。 -
访问对象:
check_weak_ptr(wp1);
调用
check_weak_ptr
函数,通过weak_ptr
的lock()
方法访问对象,如果对象存在则输出其值。 -
共享所有权:
std::shared_ptr<MyClass> sp2 = sp1;
创建另一个
shared_ptr
共享同一个对象,并输出引用计数。 -
重置
shared_ptr
:sp1.reset();
重置
sp1
,sp2
仍然持有对象的所有权,因此对象不会被释放。 -
访问对象:
check_weak_ptr(wp1);
再次调用
check_weak_ptr
函数,通过weak_ptr
访问对象。 -
释放所有
shared_ptr
:sp2.reset();
重置
sp2
,此时没有shared_ptr
持有对象,对象被销毁。 -
访问对象:
check_weak_ptr(wp1);
最后一次调用
check_weak_ptr
函数,weak_ptr
不能再访问对象,输出对象不存在。
通过这个示例,可以看到std::weak_ptr
如何用于安全地观察和访问std::shared_ptr
所管理的对象,避免循环引用并确保资源正确释放。