keywords
- std::enable_shared_from_this
- 線程安全
- std::weak_ptr
0 引言
C++11的一些高級(jí)性能,方便了我們的編程,但是面對(duì)這些高級(jí)特性的同時(shí),也給我們埋了很多坑。比如,看下面這段代碼:
#include <iostream>
#include <memory>
#include <unistd.h>
class A {
public:
A(int val):val_(val){}
std::shared_ptr<A> GetPtr(){
return std::shared_ptr<A>(this);
}
private:
int val_;
};
int main(){
auto a = std::make_shared<A>(1);
auto b = a->GetPtr();
return 0;
}
不知道你是否看出這段代碼的問(wèn)題? 我大概解釋下:使用std::shared_ptr的時(shí)候,有一個(gè)注意事項(xiàng)一定一定要避免不同的ptr指向同一個(gè)對(duì)象
這個(gè)原則,大家想想也知道,好比,一臺(tái)車賣給了2個(gè)人,那這個(gè)車到底歸誰(shuí)呢? 另外,比喻恰當(dāng)點(diǎn):當(dāng)兩個(gè)人都想賣這臺(tái)車額時(shí)候,第一個(gè)人如果賣了,第二個(gè)人拿啥賣呢?
1 方案
有問(wèn)題當(dāng)然需要解決
1.1 enable_shared_from_this
但是我們很難避免需要把this指針當(dāng)做std::shared_ptr參數(shù)傳遞的場(chǎng)景,怎么辦呢?
C++當(dāng)然針對(duì)這個(gè)問(wèn)題,是有方案的,那就是使用 std::enable_shared_from_this,上面代碼修改如下:
#include <iostream>
#include <memory>
#include <unistd.h>
class A: public std::enable_shared_from_this<A>{
public:
A(int val):val_(val){}
std::shared_ptr<A> GetPtr(){
return shared_from_this();
}
private:
int val_;
};
int main(){
auto a = std::make_shared<A>(1);
auto b = a->GetPtr();
return 0;
}
到這里,關(guān)于將this指針當(dāng)做參數(shù)傳遞的事情是解決了,但是接下來(lái),繼續(xù)看。
1.2 enable_shared_from_this 使用之坑
1.2.1 與構(gòu)造函數(shù)之坑
禁止在構(gòu)造函數(shù)中,使用。這個(gè)其實(shí)也好理解:對(duì)象都沒(méi)構(gòu)造完,哪里來(lái)的對(duì)象讓你shared呢
1.2.2 與析構(gòu)函數(shù)之坑
注意,在進(jìn)入到析構(gòu)函數(shù)之前,shared_ptr的reference count已經(jīng)是0了,此時(shí)不能在訪問(wèn)之前shared_from_this()了
1.2.3 異步訪問(wèn)之坑
我們可能會(huì)有需求,將拿到的shared_ptr對(duì)象交給異步的線程來(lái)使用,此時(shí),比如,看看下面代碼:
#include <iostream>
#include <memory>
#include <unistd.h>
#include <thread>
#include <atomic>
struct A : public std::enable_shared_from_this<A> {
A() {
inited_.store(true);
}
bool isInit() {
return inited_.load();
}
void debug() {
std::cout << "debug" << std::endl;
}
void init() {
std::shared_ptr<A> ptr = shared_from_this();
thread_ = std::thread([ptr](){
usleep(200*10000);
ptr->debug();
});
}
~A() {
std::cout << "pre ~A" << std::endl;
usleep(1000 * 1000);
inited_.store(false);
std::cout << "wait ~A" << std::endl;
if (thread_.joinable()) {
thread_.join();
}
std::cout << "post ~A" << std::endl;
}
private:
std::atomic<bool> inited_;
std::thread thread_;
};
int main() {
{
auto a = std::make_shared<A>();
a->init();
usleep(100 * 1000);
a.reset();
}
std::cout << "will exit" << std::endl;
sleep(5);
std::cout << "exit" << std::endl;
return 0;
}
發(fā)現(xiàn)沒(méi),程序執(zhí)行異常crash了。還記得嗎,對(duì)于繼承了enable_shared_from_this的對(duì)象,不能在析構(gòu)函數(shù)中使用對(duì)象,上面情況是不是命中了這個(gè)禁忌?
針對(duì)這個(gè)問(wèn)題,weak_ptr能提供一個(gè)很好的解決方案,關(guān)于weak_ptr后面會(huì)專門(mén)介紹,使用weak_ptr方案的代碼如下
#include <iostream>
#include <memory>
#include <unistd.h>
#include <thread>
#include <atomic>
struct A : public std::enable_shared_from_this<A> {
A() {
inited_.store(true);
}
bool isInit() {
return inited_.load();
}
void debug() {
std::cout << "debug" << std::endl;
}
void init() {
std::weak_ptr<A> weak_ptr = shared_from_this();
thread_ = std::thread([weak_ptr]() {
usleep(1000 * 10000);
auto this_ = weak_ptr.lock();
if (this_) {
this_->debug();
} else {
std::cout << "null of this_" << std::endl;
}
});
}
~A() {
std::cout << "pre ~A" << std::endl;
usleep(1000 * 1000);
inited_.store(false);
std::cout << "wait ~A" << std::endl;
if (thread_.joinable()) {
thread_.join();
}
std::cout << "post ~A" << std::endl;
}
private:
std::atomic<bool> inited_;
std::thread thread_;
};
int main() {
{
auto a = std::make_shared<A>();
a->init();
usleep(100 * 1000);
a.reset();
}
std::cout << "will exit" << std::endl;
sleep(5);
std::cout << "exit" << std::endl;
return 0;
}
關(guān)于weak_ptr 以及其方法 lock() ,可以先參考 std::weak_ptr