条款18:使用 std::unique_ptr 管理具备专属所有权的资源
要点速记
*std::unique_ptr 是小巧,高速、具备只移型别的智能指针,对托管资源实施专属所有权语义
*默认的,资源析构采用 delete 运算符来实现,但是可以指定自定义删除器,有状态的删除器或者函数指针实现的删除器会增加 std::unique_ptr 型别的对象尺寸,因为删除器是其一部分
*无状态的函数对象,无捕获的 lambda 表达式不会增加智能指针的尺寸,应该优先使用
*将 std::unique_ptr 转换为 std::shared_ptr 很容易实现
*常用于 工厂函数 和 pimpl实现
class Investment {
public:
Investment() = default;
~Investment() = default;
};
class Stock : public Investment {
using Investment::Investment;
};
class Bond : public Investment {
using Investment::Investment;
};
class RealEstate : public Investment {
using Investment::Investment;
};
// 默认删除器
unique_ptr<Investment> makeInvestment(int i) {
unique_ptr<Investment> pInv;
if (i < 0) {
pInv.reset(new Stock());
} else if (i == 0) {
pInv.reset(new Bond());
} else if (i > 0) {
pInv.reset(new RealEstate());
}
return pInv;
}
// 自定义删除器,注意,函数的返回类型采用auto,即自动推导
auto delInvmt = [](Investment *pInvmt) {
std::cout << "logging inof..." << std::endl;
delete pInvmt;
};
template<typename... TS>
auto makeInvestment(TS... params) {
// 自定义删除器就不能默认构造了
unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt);
if (sizeof...(params) == 1) {
pInv.reset(new Stock());
} else if (sizeof...(params) == 2) {
pInv.reset(new Bond());
} else if (sizeof...(params) == 3) {
pInv.reset(new RealEstate());
}
return pInv;
}
// 测试
void UniquePtrTest1() {
auto up1 = makeInvestment(-1);
auto up2 = makeInvestment(0);
auto up3 = makeInvestment(1);
{
auto up4 = makeInvestment("hello");
auto up5 = makeInvestment("hello", "world");
auto up6 = makeInvestment("hello", "world", "hello motherland");
}
return;
}
条款19:使用 std::shared_ptr 管理具备共享所有权的资源
要点速记
*std::shared_ptr 提供方便的手段,实现任意资源在共享所有权语义下进行生命周期管理的垃圾回收
*与std::unique_ptr 相比,std::shared_ptr 的尺寸通常是裸指针的两倍,它还会带来控制块的开销,并要求原子化的引用计数操作
*默认的资源析构通过 delete 运算符进行, 但同时也支持定制删除器, 删除器的类型对 std::shared_ptr 的型别没有影响,不是其对象的直接部分
*避免使用裸指针型别的变量来创建 std::shared_ptr 指针
class Widget {
public:
Widget() = default;
Widget(int p) : size(p) {}
private:
int size;
};
void sharedPtrTest1() {
// 自定义删除器
auto loggingDel = [](Widget *pw) {
std::cout << "logging info..." << std::endl;
delete pw;
};
// unique_ptr
unique_ptr<Widget, decltype(loggingDel)> upw1(new Widget(), loggingDel); // loggingDel是unique_ptr一部分
auto upw2(std::make_unique<Widget>()); // 默认删除器
auto upw3(std::make_unique<Widget>(1)); // 默认删除器
// shared_ptr
shared_ptr<Widget> spw(new Widget, loggingDel); // loggingDel不是shared_ptr的一部分
auto sp2(std::make_shared<Widget>()); // 默认删除器
auto sp3(std::make_shared<Widget>(1)); // 默认删除器
// 不同的自定义删除器对shared_ptr型别无影响
auto customDeleter1 = [](Widget *pw) {
std::cout << "customDeleter1 logging info..." << std::endl;
delete pw;
};
auto customDeleter2 = [](Widget *pw) {
std::cout << "customDeleter2 logging info..." << std::endl;
delete pw;
};
shared_ptr<Widget> spw11(new Widget, customDeleter1);
shared_ptr<Widget> spw22(new Widget, customDeleter2);
std::vector<shared_ptr<Widget>> vpw{spw11, spw22}; // 瞧,不同删除器没有影响,还是同一种类型
return;
}
条款20:对于类似 std::shared_ptr 但有可能空悬的指针使用 std::weak_ptr
要点速记
*使用 std::weak_ptr 来代替可能空悬的 std::shared_ptr
*std::weak_ptr 可能的用武之地包括缓存, 观察者列表, 以及避免 shared_ptr 指针环路(在树这种严格继承谱系式的数据结构中,也可直接用裸指针来实现环路避免)
void weakPtrTest1() {
auto spw = std::make_shared<Widget>();
weak_ptr<Widget> wpw(spw);
// spw = nullptr;
// weak_ptr测试指针是否空悬,有效性测试
if (wpw.expired()) {
std::cout << "the object is deleted..." << std::endl;
}
// 两种常用操作形式
// 1)lock, 若wpw失效,则spw1为空
auto spw1 = wpw.lock();
// 2)weak_ptr作为实参来构造shared_ptr,如果wpw失效,则抛出异常
shared_ptr<Widget> spw2(wpw);
return;
}
条款21:优先选用 std::make_unique 和 std::make_shared, 而非直接使用 new
要点速记
*相比于直接使用 new 表达式, make 系列函数消除了重复代码,改进了异常安全性,并且对于 make_shared 和 allcoated_shared 而言,生成的目标代码会尺寸更小,速度更快
*不适于使用 make 系列函数的场景包括需要定制删除器, 以及期望直接传递 {} 初始化物
*对于 shared_ptr, 不建议使用 make 系列函数的额外场景包括:1)自定义内存管理的类; 2)内存紧张的系统、非常大的对象、以及存在比指涉到相同对象的 shared_ptr 生存期更久的 weak_ptr
int computePriority() {}
void processWidget(shared_ptr<Widget> spw, int priority) {}
void makePtrTest1() {
// 比较
processWidget(shared_ptr<Widget>(new Widget()), computePriority()); // 潜在的资源泄露,引发两次内存分配
processWidget(std::make_shared<Widget>(), computePriority()); // 不会发生潜在资源泄露的风险,一次内存分配
// 需要自定义删除器的场景不适合使用make系函数
auto widgetDeleter = [](Widget *pw) { delete pw; };
shared_ptr<Widget> spw(new Widget, widgetDeleter);
// 需要传递{}之物做初始化
auto upv = std::make_unique<vector<int>>(5, 20); // {20,20,20,20,20,20}
auto spv = std::make_shared<vector<int>>(5, 20); // {20,20,20,20,20,20}
auto iniList = {5, 20};
auto spv1 = std::make_shared<vector<int>>(iniList); // {5, 20}
// make_shared创建的内存是依次性分配的,存在有 weak_ptr 指涉相同对象的情况下,shared_ptr析构完成本来应该释放的对象内存没有释放
// 因为这个时候可能weak_ptr的生成期仍然存在,导致控制块需要使用
// todo
// processWidget(shared_ptr<Widget>(new Widget(), widgetDeleter), computePriority());的改进
processWidget(shared_ptr<Widget>(new Widget(), widgetDeleter), computePriority()); // 存在资源泄露风险
// 改进一,不存在资源泄露问题
std::shared_ptr<Widget> spw1(new Widget, widgetDeleter);
processWidget(spw1, computePriority()); // shared_ptr的拷贝涉及引用计数变化,涉及到原子操作,需要考虑性能问题
// 改进二,改进性能
processWidget(std::move(spw1), computePriority()); // 提示性能
return;
}