一 智能指针使用概述
1.使用场景
1.1 unique_ptr
1.1.1 概念
std::unique_ptr
是通过指针占有并管理另一对象,并在 unique_ptr
离开作用域时释放该对象的智能指针。
std::unique_ptr
常用于管理对象的生存期,包含:
- 通过正常退出和经由异常退出两者上的受保证删除,提供异常安全,给处理拥有动态生存期的对象的类和函数
- 传递独占的拥有动态生存期的对象的所有权到函数
- 从函数获得独占的拥有动态生存期对象的所有权
- 作为具移动容器的元素类型,例如保有指向动态分配对象的指针的 std::vector (例如,若想要多态行为)
1.1.2 使用追述
struct B {
virtual void bar() { std::cout << "B::bar\n"; }
virtual ~B() = default;
};
struct D : B
{
D() { std::cout << "D::D\n"; }
~D() { std::cout << "D::~D\n"; }
void bar() override { std::cout << "D::bar\n"; }
};
// 消费 unique_ptr 的函数能以值或以右值引用接收它
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
p->bar();
return p;
}
void close_file(std::FILE* fp) { std::fclose(fp); }
int main()
{
std::cout << "unique ownership semantics demo\n";
{
auto p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr
auto q = pass_through(std::move(p));
assert(!p); // 现在 p 不占有任何内容并保有空指针
q->bar(); // 而 q 占有 D 对象
} // ~D 调用于此
唯一性指针删除了拷贝构造和赋值重载,只保留了移动构造和移动赋值:
auto q = pass_through(std::move(p));
此代码也可运行通过:
std::unique_ptr<D>res(std::move(p));
唯一性指针可以设置自定义删除器
int main()
{
std::cout << "Custom lambda-expression deleter demo\n";
{
std::unique_ptr<D, std::function<void(D*)>> p(new D, [](D* ptr)
{
std::cout << "destroying from a custom deleter...\n";
delete ptr;
}); // p 占有 D
p->bar();
} // 调用上述 lambda 并销毁 D
}
执行结果:
Custom lambda-expression deleter demo
D::D
D::bar
destroying from a custom deleter...
D::~DE:\ccc\helloc\Debug\helloc.exe (进程 9996)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
1.2 shared_ptr
1.2.1概念
std::shared_ptr
是通过指针保持对象共享所有权的智能指针。多个 shared_ptr
对象可占有同一对象。下列情况之一出现时销毁对象并解分配其内存:
- 最后剩下的占有对象的
shared_ptr
被销毁; - 最后剩下的占有对象的
shared_ptr
被通过 operator= 或 reset() 赋值为另一指针。
shared_ptr
能在存储指向一个对象的指针时共享另一对象的所有权。此特性能用于在占有其所属对象时,指向成员对象。存储的指针为 get() 、解引用及比较运算符所访问。被管理指针是在 use_count 抵达零时传递给删除器者。
1.2.2循环引用问题
代码:
struct D;
using namespace std;
struct B {
std::shared_ptr<D>mb;//观察res1
void bar() { std::cout << "B::bar\n"; }
B() { std::cout << "B::bar\n"; }
B(std::shared_ptr<D>p) :mb(p){ std::cout << "B::create\n"; }
~B() { std::cout << "B::~D\n"; }
};
struct D
{
D() { std::cout << "D::CREATE\n"; }
std::shared_ptr<B>md;//观察res2
D(std::shared_ptr<B>p) :md(p){ std::cout << "D::D\n"; }
~D() { std::cout << "D::~D\n"; }
void bar() { std::cout << "D::bar\n"; }
};
int main()
{
shared_ptr<D>res1(new D);
shared_ptr<B>res2 = std::make_shared<B>(res1);
res1->md = res2;
}
问题造成的结果:
无法释放被管理的对象。
问题的原因:
1、 此两个智能指针释放时,uses减少1为1,并不会释放ptr资源.
问题的解决办法:
使用弱引用智能指针。
1.uses和weaks初始都为1。
2.在相互赋值智能指针成员对象时发生:
根据函数入栈顺序,先析构res1,uses由1减少1为0,weaks由2减少为1;析构指向的资源,并将ptr中rep指向的weaks引用计数减少1为1;
之后析构res2,uses由1减少为0,weaks由1减少为0,所以释放引用计数器;析构ptr指向的资源,将weaks由1降为0;释放引用计数器。
1,2.3线程安全
1.2.3.1 概述
多个线程能在 shared_ptr
的不同实例上调用所有成员函数(包含复制构造函数与复制赋值)而不附加同步,即使这些实例是副本,且共享同一对象的所有权。若多个执行线程访问同一 shared_ptr
而不同步,且任一线程使用 shared_ptr
的非 const 成员函数,则将出现数据竞争;原子函数的 shared_ptr 特化能用于避免数据竞争。或者是进行同步操作。
1.2.3.2 多线程智能指针的写入会导致问题
多线程下的智能指针读操作不会产生线程安全问题。
多线程下的智能指针写操作会产生线程安全问题。
三 共享型智能指针使用陷阱
最常见的使用错误:
1.使用相同的内置指针初始化多个智能指针
这会导致多个智能指针并不会指向同一个引用计数器。
2.不要用裸指针作为智能指针的参数传入
void func(shared_ptr<int> ptr) {
return;
}
int* p = new int(100);//裸指针
func(shared_ptr<int>(p));
ptr在函数栈被释放时,资源也被立即释放。而你之后再继续使用那不就爆炸了嘛。
3.不要轻易使用get()取得裸指针。
智能指针就是怕裸指针出事情才出现的。所以最好不要做此操作。
4.使用shared_from_this返回this是安全的
其他人在使用this时,是否知道this是否已经失效?失效后使用的后果是很大的。