讀書筆記 《Effective modern C++》之 Smart pointer(四)
Item 18: Use std::unique_ptr for exclusive-ownership resource management.
Item 19: Use std::shared_ptr for shared-ownership resource management.
Item 20: Use std::weak_ptr for std::shared_ptr like pointers that can dangle.
Item 21: Prefer std::make_unique and std::make_shared to direct use of new.
make函式的缺點
- 不能傳入自定義deleter
- make函式使用括號初始化,比如std::make_unique<std::vector
>(10, 20)建立了一個有著20個值為10的元素的vector,而不是建立了{10, 20}這麼兩個元素的vector。 - 管理的物件和control block分配在一塊記憶體上,導致被管理物件和control block佔用的記憶體要同時回收。即,如果還有std::weak_ptr存在,control block就要在,物件佔用的記憶體也沒辦法回收。比如shared_count為0,只存在weak_count,雖然expired了還是不能釋放物件的記憶體。
Item 22: When using the Pimpl Idiom, define special member functions in the implementation file.
Pimpl 是一種介面和實現分離的方法,減少類的dependency來加快編譯的方法。
簡單的說就是
- 把類所有的私有成員放到新宣告的類中 - like PrivateImpl class
- 在類的檔案中,只做向前宣告 PrivateImpl * // 向前宣告,需要是指標
- 在相應的cpp檔案中實現PrivateImpl
- 這樣改變了PrivateImpl的實現,類也不需要重新編譯(類中的PrivateImpl只是一個向前宣告,介面並沒有改變)
常見的非pimpl實現
// Fridge.h
#include "Engine.h"
class Fridge
{
public:
void coolDown();
private:
Engine engine_;
};
// Fridge.cpp
#include "Fridge.h"
void Fridge::coolDown()
{
/* ... */
}
pimpl實現介面和實現分離後不需要再Fridge.h中引入Engine.h(所以Engine.h修改不影響Fridge類,而Fridge類只負責forward the call和管理impl_的資源)。
pointers only need a forward declaration to compile。所以Fridge不需要知道FridgeImpl所有的definition
// Fridge.h
// 介面實現分離
class Fridge
{
public:
Fridge();
~Fridge();
void coolDown();
private:
class FridgeImpl;
FridgeImpl* impl_;
};
// Fridge.cpp
#include "Engine.h"
#include "Fridge.h"
class Fridge::FridgeImpl
{
public:
void coolDown()
{
/* ... */
}
private:
Engine engine_;
};
Fridge::Fridge() : impl_(new FridgeImpl) {}
Fridge::~Fridge()
{
delete impl_;
}
void Fridge::coolDown()
{
impl_->coolDown();
}
如果轉成unique_ptr有個坑需要注意。下面的實現是無法編譯的,因為如果指向的型別不完整時(只有向前宣告),unique_ptr會拒絕編譯。
deleting a pointer leads to undefined behaviour if:
- this pointer has type void*, or
- the type pointed to is incomplete, that is to say is only forward declared, like FridgeImpl in our header file.
std::unique_ptr happens to check in its destructor if the definition of the type is visible before calling delete. So it refuses to compile and to call delete if the type is only forward declared.
Since we removed the declaration of the destructor in the Fridge class, the compiler took over and defined it for us. But compiler-generated methods are declared inline, so they are implemented in the header file directly. And there, the type of FridgeImpl is incomplete. Hence the error.
就是一個delete不完整類指標的問題
https://blog.csdn.net/weixin_33709364/article/details/90326569
// Fridge.h
#include <memory>
class Fridge
{
public:
Fridge();
void coolDown();
private:
class FridgeImpl;
std::unique_ptr<FridgeImpl> impl_;
};
// Fridge.cpp
#include "Engine.h"
#include "Fridge.h"
class FridgeImpl
{
public:
void coolDown()
{
/* ... */
}
private:
Engine engine_;
};
Fridge::Fridge() : impl_(new FridgeImpl) {}
修改方法就是顯示宣告Fridge的解構函式,並且把實現放到FridgeImpl實現之後
#include <memory>
class Fridge
{
public:
Fridge();
~Fridge();
void coolDown();
private:
class FridgeImpl;
std::unique_ptr<FridgeImpl> impl_;
};
#include "Engine.h"
#include "Fridge.h"
class FridgeImpl
{
public:
void coolDown()
{
/* ... */
}
private:
Engine engine_;
};
Fridge::Fridge() : impl_(new FridgeImpl) {}
Fridge::~Fridge() = default;
記得如果顯示聲明瞭解構函式,需要拷貝構造和移動構造時候就要自己補上。要注意的是,使用shared_ptr不需要這樣。
class Widget { // in "widget.h"
public:
Widget();
… // no declarations for dtor or move operations
private:
struct Impl;
std::shared_ptr<Impl> pImpl; // std::shared_ptr
}; // instead of std::unique_ptr
Widget w1;
auto w2(std::move(w1)); // move-construct w2
w1 = std::move(w2); // move-assign w1
這是因為shared_ptr和unique_ptr對自定義deleter的支援實現是不一樣的。unique_ptr的deleter是模板引數(EBO優化),而shared_ptr的deleter是建構函式的一部分(成員物件)。
https://fuzhe1989.github.io/2017/05/19/cpp-different-role-of-deleter-in-unique-ptr-and-shared-ptr/
https://www.bfilipek.com/2018/01/pimpl.html
https://light-city.club/sc/src_analysis/stl/談談STL設計之EBO優化/