c++智能指针的使用

官方参考

普通指针的烦恼:内存泄漏,多次释放,提前释放

智能指针 负责自动释放所指向的对象。

三种智能指针 shared_ptr,unique_ptr,weak_ptr;

将shared_ptr存放在一个容器中,不再需要它的时候,要erase掉。

allocator负责封装堆内存管理的对象,它们在整个标准库中使用,特别是STL容器使用它们来管理容器内部的所有内存分配,大部份情况下,程序员不用理会,标准容器使用默认的分配器称为std :: allocator。

shared_ptr

shared_ptr

多个指针指向相同的对象;

使用引用计数,引用计数是线程安全的,但是对象的读写需要加锁。

不可以直接将指针直接赋值给一个智能指针,因为指针指针是一个类。

get获取原始指针

最大的陷阱就是循环引用,这会导致内存无法正确释放,导致内存泄漏

#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()
{
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";
}

可能的输出:

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

weak_ptr

是为了配合shared_ptr而引入的一种智能指针,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。

weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。

成员函数expired()的功能等价于use_count()==0,

weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象

#include <iostream>
#include <memory> std::weak_ptr<int> gw; void observe()
{
std::cout << "use_count == " << gw.use_count() << ": ";
if (auto spt = gw.lock()) { // 使用之前必须复制到 shared_ptr
std::cout << *spt << "\n";
}
else {
std::cout << "gw is expired\n";
}
} int main()
{
{
auto sp = std::make_shared<int>(42);
gw = sp; observe();
} observe();
}

输出:

use_count == 1: 42
use_count == 0: gw is expired

unique_ptr

unique_ptr

唯一拥有对象

通过reset方法重新指定

通过release方法释放所有权

#include <iostream>
#include <vector>
#include <memory>
#include <cstdio>
#include <fstream>
#include <cassert>
#include <functional> 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 调用于此 std::cout << "Runtime polymorphism demo\n";
{
std::unique_ptr<B> p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr
// 作为指向基类的指针
p->bar(); // 虚派发 std::vector<std::unique_ptr<B>> v; // unique_ptr 能存储于容器
v.push_back(std::make_unique<D>());
v.push_back(std::move(p));
v.emplace_back(new D);
for(auto& p: v) p->bar(); // 虚派发
} // ~D called 3 times std::cout << "Custom deleter demo\n";
std::ofstream("demo.txt") << 'x'; // 准备要读的文件
{
std::unique_ptr<std::FILE, void (*)(std::FILE*) > fp(std::fopen("demo.txt", "r"),
close_file);
if(fp) // fopen 可以打开失败;该情况下 fp 保有空指针
std::cout << (char)std::fgetc(fp.get()) << '\n';
} // fclose() 调用于此,但仅若 FILE* 不是空指针
// (即 fopen 成功) 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 std::cout << "Array form of unique_ptr demo\n";
{
std::unique_ptr<D[]> p{new D[3]};
} // 调用 ~D 3 次
}

输出:

unique ownership semantics demo
D::D
D::bar
D::bar
D::~D
Runtime polymorphism demo
D::D
D::bar
D::D
D::D
D::bar
D::bar
D::bar
D::~D
D::~D
D::~D
Custom deleter demo
x
Custom lambda-expression deleter demo
D::D
D::bar
destroying from a custom deleter...
D::~D
Array form of unique_ptr demo
D::D
D::D
D::D
D::~D
D::~D
D::~D

shared_ptr循环引用的内存泄漏问题

如下对象建模——家长与子女:a Parent has a Child, a Child knowshis/her Parent。

从程序的运行中可以看到最终资源没有得到释放。

一个智能指针在创建一个对象的时候初始化引用计数为 1,并把自己的指针指向创建的对象。但这个引用计数在何处?在智能指针内部?非也,这个计数是一个单独的对象来实现的,如图1,当另外一个智能指针指向这个对象的时候,便找到与这个对象对应的计数对象,并加一个引用,即 use_count++。这样多个智能指针对象便可以使用相同的引用计数。

下面程序中,当指针p释放时,由于指针c->ParentPtr还在引用着new Child,所以这时(new Child)的use_count从2减为1。同理当指针c释放时,由于p->ChildPtr还在引用着new Parent,所以这时(new Parent)的use_count从2减为1。最终,内存没有被释放完全。

class Child;
class Parent; class Parent {
private:
std::shared_ptr<Child> ChildPtr;
public:
void setChild(std::shared_ptr<Child> child) {
this->ChildPtr = child;
} void doSomething() {
if (this->ChildPtr.use_count()) {
}
} ~Parent() {}
}; class Child {
private:
std::shared_ptr<Parent> ParentPtr;
public:
void setPartent(std::shared_ptr<Parent> parent) {
this->ParentPtr = parent;
}
void doSomething() {
if (this->ParentPtr.use_count()) {
}
}
~Child() {}
}; int main() {
std::weak_ptr<Parent> wpp;
std::weak_ptr<Child> wpc;
{
std::shared_ptr<Parent> p(new Parent);
std::shared_ptr<Child> c(new Child);
std::cout << "p.use_count() = " << p.use_count() << std::endl;
std::cout << "c.use_count() = " << c.use_count() << std::endl;
p->setChild(c);
c->setPartent(p);
std::cout << "p.use_count() = " << p.use_count() << std::endl;
std::cout << "c.use_count() = " << c.use_count() << std::endl;
wpp = p;
wpc = c; std::cout << "p.use_count() = " << p.use_count() << std::endl; // 2
std::cout << "c.use_count() = " << c.use_count() << std::endl; // 2
cout<<endl; }
std::cout << "p.use_count() = " << wpp.use_count() << std::endl; // 1
std::cout << "c.use_count() = " << wpc.use_count() << std::endl; // 1
return 0;
}

运行结果

p.use_count() = 1
c.use_count() = 1
p.use_count() = 2
c.use_count() = 2
p.use_count() = 2
c.use_count() = 2 p.use_count() = 1
c.use_count() = 1

shared_ptr循环引用的内存泄漏问题解决

如下,在两个需要互相引用的类的内部,使用weak_ptr智能指针引用对方,来避免循环引用导致的内存泄漏问题。

#include <iostream>
#include <memory> class Child;
class Parent; class Parent {
private:
//std::shared_ptr<Child> ChildPtr;
std::weak_ptr<Child> ChildPtr;
public:
void setChild(std::shared_ptr<Child> child) {
this->ChildPtr = child;
} void doSomething() {
//new shared_ptr
if (this->ChildPtr.lock()) { }
} ~Parent() {
}
}; class Child {
private:
std::shared_ptr<Parent> ParentPtr;
public:
void setPartent(std::shared_ptr<Parent> parent) {
this->ParentPtr = parent;
}
void doSomething() {
if (this->ParentPtr.use_count()) { }
}
~Child() {
}
}; int main() {
std::weak_ptr<Parent> wpp;
std::weak_ptr<Child> wpc;
{
std::shared_ptr<Parent> p(new Parent);
std::shared_ptr<Child> c(new Child);
p->setChild(c);
c->setPartent(p);
wpp = p;
wpc = c;
std::cout << p.use_count() << std::endl; // 2
std::cout << c.use_count() << std::endl; // 1
}
std::cout << wpp.use_count() << std::endl; // 0
std::cout << wpc.use_count() << std::endl; // 0
return 0;
}

运行结果

2100

更多编程资料详见公众号 xutopia77

最新文章

  1. js储存参数的数组arguments
  2. ExtJS基础知识总结:常用控件使用方式(一)
  3. 李洪强iOS经典面试题153- 补充
  4. APP敏捷测试,测试和开发并行!
  5. HDU-3548-Enumerate the Triangles
  6. 谢谢博客-园,让我不再有开源AYUI的想法
  7. 在IIS7中使用ARR(Application Request Routing)反向代理虚拟目录到Nodejs站点
  8. Spring的属性编辑器
  9. 利用反射得到android存储路径
  10. java内部类和外部类
  11. 常规SQL注入脚本
  12. javascript 最常用的技巧整理
  13. 【Delphi】无标题移动窗体
  14. IOS 手机端搜索硬件设备 --- 物联网
  15. js获取浏览器宽高
  16. 抓包工具Wireshark的使用
  17. Asp.net导出Excel/Csv文本格式数据
  18. SpringCloud入门
  19. cookie,localStorage和sessionStorage区别
  20. Python-Numpy数组计算

热门文章

  1. Jenkins插件维护
  2. Python __new__ 方法解释与使用
  3. 粒子群算法-PSO
  4. axb_2019_fmt32
  5. Kubernetes 集群无损升级实践 转至元数据结尾
  6. 『学了就忘』Linux日志管理 — 90、Linux中日志介绍
  7. 10-2 bonding
  8. Redis报错:RDB snapshots, but it is currently not able to persist on disk 处理
  9. libuv解析
  10. Python处理utf-8 添加和删除BOM头