文章目录
- 介绍、分类
- 多线程、多进程
- 开源库
- 0拷贝
概述
C++智能指针位于标准库头文件 ,此头文件是动态内存管理库的一部分。
enable_shared_from_this
https://zh.cppreference.com/w/cpp/memory/enable_shared_from_this
https://blog.csdn.net/caoshangpa/article/details/79392878
https://blog.csdn.net/zk3326312/article/details/79108690
sharedptr线程安全
2.1 std::unique_ptr
2.1.1 概述
std::unique_ptr系C++11引入的智能指针,拥有资源的唯一所有权,头文件 #include <memory>
。
unique_ptr指针指向的堆内存空间的引用计数为 1,如果unique_ptr 指针放弃对所指堆内存空间的所有权,那么该空间会被立即释放回收。
那么,什么时候使用unique_ptr呢?简单来说:可以考虑将动态分配的有唯一所有者的资源保存在unique_ptr的实例中。
unique_ptr的特点:
- unique_ptr 对象不能进行赋值操作和移动操作。
- unique是独特的,唯一的意思,因此表示它独占一个对象。
- unique_ptr和share_ptr类型有很大的不同,share_ptr允许多个指针指向同一个对象,而unique_ptr在某一时刻只能有一个指针指向该对象。两个unique不能指向同一个对象。
- unique_ptr对象中保存指向某个对象的指针,当它本身被删除或者离开其作用域就会被自动释放其指向对选哪个所占有的资源。
2.1.2 实现原理
- unique_ptr 是 C++ 11 提供的用于防止内存泄漏的智能指针中的一种实现,即使在异常发生时也可帮助避免资源泄露。
- unique_ptr实现了独享被管理对象指针的概念,这意味这它可确保一个对象和其对应的资源同一时间只被一个pointer拥有。一旦拥有者被销毁或者变成empty或者开始拥有另一个对象,先前拥有的那个对象就会被销毁,其任何相应资源亦会被释放。
- unique_ptr具有->和*运算符重载符,因此它可以像普通指针一样使用。
2.1.3 使用场景
- 为动态申请的资源提供安全保证
- 返回函数内存申请资源的所有权
- 在容器中保存指针
- 动态管理数组
先看不使用智能指针,写代码时的痛点,有可能忘记delete对象,在某处return的时候,或者在某处抛出异常,导致末尾的delete语句就没机会被调用,导致内存泄漏。在还是只new一个对象,如果new2,3甚至更多对象,那管理起来,代码变的比较复杂,而且累赘。
这是一种不好的编程风格,应该避免,因为它复杂而又容易出错。
#include <memory>
#include<iostream>
using namespace std;
class A {};
int main()
{
A* ptrA = new A;
try
{
//...
//...
}
catch (...)
{
delete ptrA; //1
throw;
}
delete ptrA; //2
return 0;
}
了解了这个痛点,那么本篇的主角unique_ptr就该闪亮登场了。
unique_ptr对象可以在自身被销毁时释放其所指向的数据。并且unique_ptr它所指向的对象只有一个拥有者。
上面糟心的代码就可以用unique_ptr来优化,在也不需要delete和catch子句。
#include <memory>
#include<iostream>
using namespace std;
class A {};
int main()
{
unique_ptr<A> upA(new A);
//...
return 0;
}
2.1.4 unique_ptr的目的
- 获取某些资源
- 执行某些操作
- 将取得的资源释放掉
2.1.5 常用操作(初始化、移动赋值)
- 初始化创建
- 移动赋值
- 销毁/释放
- 作为参数、作为返回值
如何初始化一个std::unique_ptr对象?
方法一:
std::unique_ptr<int> sp(new int(12345));
方法二:
std::unique_ptr<int> sp;
sp.reset(new int(12345));
方法三:
std::unique_ptr<int> sp = std::make_unique<int>(12345);
以上三种方式均可,其中,方法三是C++14新增的,通过std::make_unique方法来创建std::unique_ptr对象。
unique_ptr不支持普通的拷贝和赋值
需要特别注意的是,由于unique_ptr“独有”的特点,它不允许进行普通的拷贝或赋值,例如:
std::unique_ptr<int> up0;
std::unique_ptr<int> up1(new int(1111));
up0 = up1 //错误,不可赋值
std::unique_ptr<int> up2(up1);//错误,不支持拷贝
移动unique_ptr的对象
虽然unique_ptr独享对象,但是也可以移动,即转移控制权。如:
std::unique_ptr<int> up1(new int(42));
std::unique_ptr<int> up2(up1.release());
// up2接受up1 release之后的指针,或者:
std::unique_ptr<int> up2;
up2.reset(up1.release());
// 或者使用move:
std::unique_ptr<int> up2(std::move(up1));
释放指向的对象
一般来说,unique_ptr被销毁时(如离开作用域),对象也就自动释放了,也可以通过其他方式下显示释放对象。如:
up = nullptr;//置为空,释放up指向的对象
up.release();//放弃控制权,返回裸指针,并将up置为空
up.reset();//释放up指向的对象
可以看到release和reset的区别在于,前者会释放控制权,返回裸指针,你还可以继续使用。而后者直接释放了指向对象。
2.1.6 示例
#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 次
}
2.2 std::shared_ptr
- C++11 shared_ptr智能指针(超级详细)
- shared_ptr基本用法和原理(共享指针)
- shared_ptr是线程安全的吗?
- https://zh.cppreference.com/w/cpp/memory/shared_ptr
常规用法
- 初始化
- 拷贝构造/移动
- 销毁释放
- 参数、返回值、匿名函数
- 嵌套
实例
#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";
}