Item 18: Use std::unique_ptr for exclusive-ownership resource management.
Effective Modern C++ Item 18 的学习和解读。
原始指针非常灵活,但是使用陷阱多,容易出错,智能指针则更容易使用。本文介绍的智能指针是 std::unique_ptr。
独占所有权
std::unique_ptr 表现出独占所有权的语义。一个非空的 std::unique_ptr 总是对它指向的资源拥有独占所有权,它不共享它指向的资源给其他指针。因此,无法通过值传递 std::unique_ptr 给函数,也不允许复制 std::unique_ptr。看下面的例子,注意 std::make_unique 在 C++14 才开始支持,从报错信息也可以看到拷贝构造函数是 delete 的。
#include<iostream>
#include<memory>
int main()
{
std::unique_ptr<int> pInt(new int(5));
std::unique_ptr<int> pInt1(pInt);
}
main.cpp: In function 'int main()':
main.cpp:8:36: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
8 | std::unique_ptr<int> pInt1(pInt);
| ^
In file included from /usr/local/include/c++/11.2.0/memory:76,
from main.cpp:2:
/usr/local/include/c++/11.2.0/bits/unique_ptr.h:468:7: note: declared here
468 | unique_ptr(const unique_ptr&) = delete;
|
std::unique_ptr 是 move-only 类型,可以 move 它的控制权,原 std::unique_ptr 则变为空指针。看下面的例子:
#include<iostream>
#include<memory>
int main()
{
std::unique_ptr<int> pInt(new int(5));
std::unique_ptr<int> pInt2 = std::move(pInt);
std::cout << *pInt2 << std::endl;
std::unique_ptr<int> pInt3(std::move(pInt2));
}
std::unique_ptr 虽然不支持复制,但有个例外:可以从函数返回一个 std::unique_ptr。
#include<iostream>
#include<memory>
std::unique_ptr<int> func(int x)
{
std::unique_ptr<int> pInt(new int(x));
return pInt;
}
int main() {
int x = 5;
std::unique_ptr<int> p = func(x);
std::cout << *p << std::endl;
}
占用内存的大小
相较于其他智能指针,std::unique_ptr 有一个优势:在不自定义删除器的情况下,std::unique_ptr 的内存占用几乎和原始指针一致。
#include<iostream>
#include<memory>
int main() {
int *p = new int(5);
std::unique_ptr<int> pu(new int(6));
std::cout << sizeof(p) << ":" << sizeof(pu) << std::endl;
return 0;
}
std::unique_ptr 内部几乎不用维护其他信息(std::shared_ptr 需要维护引用计数),当它离开作用域,是通过 delete 删除指向的资源。但是,如果自定义了删除器,则会增加内存占用。
#include<iostream>
#include<memory>
int main() {
int c = 2;
int d = 3;
auto delint = [&](int *p) {
std::cout << "c = " << c << std::endl;
std::cout << "d = " << d << std::endl;
std::cout << "deleter" << std::endl;
delete p;
};
std::unique_ptr<int, decltype(delint)> p(new int(10), delint);
std::cout << sizeof(p) << std::endl;
return 0;
}
24
c = 2
d = 3
deleter
一个典型应用
std::unique_ptr 的一个典型应用是作为一个工厂函数的返回类型(指向类层次中的对象)。这里直接使用这里的代码作为例子:
#include<iostream>
#include<memory>
using namespace std;
class Investment
{
public:
virtual ~Investment() = default;
public:
virtual void doWork() = 0;
};
class Stock : public Investment
{
public:
virtual void doWork() override {
cout << "Stock doWork....\n";
}
};
class Bond : public Investment
{
public:
virtual void doWork() override {
cout << "Bond doWork....\n";
}
};
enum class InvestType {
INVEST_TYPE_STOCK,
INVEST_TYPE_BOND,
};
auto makeInvestment(InvestType type)
{
auto delInvmt = [](Investment *pInvestment) {
cout << "delInvmt called...." << endl;
delete pInvestment;
};
unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt);
switch (type)
{
case InvestType::INVEST_TYPE_STOCK:
pInv.reset(new Stock);
break;
case InvestType::INVEST_TYPE_BOND:
pInv.reset(new Bond);
break;
}
return pInv;
}
void test()
{
{
auto pInv = makeInvestment(InvestType::INVEST_TYPE_STOCK);
if (pInv)
{
pInv->doWork();
}
}
cout << "----------------\n";
{
auto pInv = makeInvestment(InvestType::INVEST_TYPE_BOND);
auto pInv2 = move(pInv);
cout << "after move pInv to pInv2 \n";
if (!pInv)
{
cout << "pInv is empty \n";
}
if (pInv2)
{
cout << "pInv2 is valid \n";
pInv2->doWork();
}
}
cout << "----------------\n";
{
shared_ptr<Investment> pInv = makeInvestment(InvestType::INVEST_TYPE_BOND);
pInv->doWork();
}
}
int main () {
test();
return 0;
}
Stock doWork....
delInvmt called....
----------------
after move pInv to pInv2
pInv is empty
pInv2 is valid
Bond doWork....
delInvmt called....
----------------
Bond doWork....
delInvmt called....
杂项
std::unique_ptr 通过 std::unique_ptr<T[]> 形式支持指向数组,并通过 delete [] 释放资源。
#include<iostream>
#include<memory>
int main()
{
std::unique_ptr<int[]> p(new int[5] {1, 2, 3, 4, 5});
p[0] = 0;
}
std::unique_ptr 可以直接隐式转换为 std::shared_ptr。
std::shared_ptr<Investment> sp =
makeInvestment( arguments );
总结一下:
- std::unique_ptr 是一个小的、快的、move-only 的智能指针,它能用来管理资源,并且独占资源的所有权。
- 默认情况下,std::unique_ptr 资源的销毁是用 delete 进行的,但也可以用户自定义 deleter。用带状态的 deleter 和函数指针作为 deleter 会增加 std::unique_ptr 对象的大小。
- 很容易将 std::unique_ptr 转换为 std::shared_ptr。
|