专属所有权:unique_ptr
我们大多数场景下用到的应该都是 unique_ptr。
// C++11
unique_ptr<string> p1;
p1.reste(new string("hello world"));
// C++11
unique_ptr<string> p1(new string("hello world"));
// C++14
auto w = make_unique<Wight>();
unique_ptr 代表的是专属所有权,即由 unique_ptr 管理的内存,只能被一个对象持有,所以,unique_ptr 不支持复制和赋值,如下:
auto w = std::make_unique<Wight>();
auto w2 = w; // 编译错误
unique_ptr 在默认情况下和裸指针的大小是一样的。
unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可自定义删除器可指定其他操作)。
在unique_ptr生命周期内,可以改变unique_ptr所指对象:
1、通过reset方法重新指定、
2、通过release方法释放所有权、
3、通过移动语义转移所有权。
u = nullptr; // 释放u指向的对象,并将u置为空
u.release() // u放弃对指针的控制权,返回指针,并将u置为空
unique_ptr<string> p2(p1.release()) // 将p1置为空,并将资源所有权转移给p2
u.reset() // 释放u指向的对象
u.reset(q) // 令u释放原管理的对象,重新管理q资源
p2.reset(p1.release())
unique_ptr<string> p2 = std::move(p1);
共享所有权:shared_ptr
代表的是共享所有权,即多个 shared_ptr 可以共享同一块内存,shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存,每使用它一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。
shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化
shared_ptr p(new int(11));
也可以使用make_shared
函数初始化。
auto w = make_shared<Wight>();
不能将指针直接赋值给一个shared_ptr
指针,一个是类,一个是指针。
std::shared_ptr<int> p4 = new int(1) // 错误写法
-
get函数获取原始指针,注意不要使用get初始化另一个智能指针或为智能指针赋值
shared_ptr<int> p(new int(42)); int *q = p.get(); shared_ptr<int> p(p.get()) // 错误 ,原因同下 shared_ptr<int> p2(q) // 错误, 原因同下
-
注意不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存。
int *p = new int(10); shared_ptr<int> shp(p); shared_ptr<int> shp2(p); // 两次释放同一内存
-
注意不要混合使用普通指针和智能指针
int *x (new int(42)); process(shared_ptr<int>(x)); // 临时变量,内存会被释放掉 int j = *x;
注意避免循环引用,shared_ptr的一个最大的陷阱是循环引用,循环引用会导致堆内存无法正确释放,导致内存泄漏。循环引用稍后介绍。
weak_ptr的使用
weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。
-
weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。
shared_ptr<int> sp = make_shared<int>(10); wak_ptr<int> w(sp); w = sp;
-
weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。
if (shared_ptr<int> np = wp.lock()) { ...... }
循环引用
TODO
智能指针与多线程
有两个指针p1 和 p2,都指向同一个堆上的对象object,假设线程A通过p1指针将对象销毁了,那p2就成了空悬指针,这时一种典型的C/C++内存错误。
下面的例子:展现了这种内存错误。
struct client
{
public:
client() : m_name(new string("Bob")) { }
~client() { delete m_name; cout << "~client()" << endl; }
string *m_name;
};
class client_manager
{
public:
void and_client(int client_id)
{
lock_guard<mutex> lk(m_lock);
m_client_map.insert({client_id, new client()});
}
void remove_client(int client_id)
{
lock_guard<mutex> lk(m_lock);
auto it = m_client_map.find(client_id);
if (it != m_client_map.end())
{
m_client_map.erase(it);
delete it->second; // 销毁
}
}
bool find_client(int client_id, client *&ptr)
{
bool result = false;
lock_guard<mutex> lk(m_lock);
auto it = m_client_map.find(client_id);
if (it != m_client_map.end())
{
result = true;
ptr = it->second;
}
return result;
}
private:
mutex m_lock;
map<int, client *> m_client_map;
};
int main()
{
client_manager manager;
int id = 1;
thread t1([&manager, &id]() {
manager.and_client(id++);
});
t1.join();
thread t2([&manager]() {
this_thread::sleep_for(chrono::seconds(2)); // 模拟OS调度
manager.remove_client(1);
});
thread t3([&manager]() {
client *p1 = NULL;
if (manager.find_client(1, p1))
{
this_thread::sleep_for(chrono::seconds(4)); // 模拟OS调度
(*p1->m_name) = "world"; // 这里会段错误,因为p1 所指的对象在t2线程已经销毁了。
}
});
t2.join();
t3.join();
}
所以要想安全的销毁对象,最好在别人(线程)都看不到的情况下,偷偷的做,这也正是GC的原理,也符合shared_ptr 的 用法。
利用 shared_ptr 改造上面的错误。
class client_manager
{
public:
void and_client(int client_id)
{
lock_guard<mutex> lk(m_lock);
shared_ptr<client> p_client(new client());
m_client_map.insert({client_id, p_client});
}
void remove_client(int client_id)
{
lock_guard<mutex> lk(m_lock);
auto it = m_client_map.find(client_id);
if (it != m_client_map.end())
{
m_client_map.erase(it);
}
}
bool find_client(int client_id, shared_ptr<client> &ptr)
{
bool result = false;
lock_guard<mutex> lk(m_lock);
auto it = m_client_map.find(client_id);
if (it != m_client_map.end())
{
result = true;
ptr = it->second;
}
return result;
}
private:
mutex m_lock;
map<int, shared_ptr<client>> m_client_map;
};
int main()
{
client_manager manager;
int id = 1;
thread t1([&manager, &id]() {
manager.and_client(id++);
});
t1.join();
thread t2([&manager]() {
this_thread::sleep_for(chrono::seconds(2)); // 模拟OS调度
manager.remove_client(1);
});
thread t3([&manager]() {
shared_ptr<client> p1;
if (manager.find_client(1, p1))
{
cout << p1.use_count() << endl;
this_thread::sleep_for(chrono::seconds(4)); // 模拟OS调度
cout << p1.use_count() << endl;
(*p1->m_name) = "world";
}
else
{
cout << "not found" << endl;
}
});
t2.join();
t3.join();
}
实现
TODO