mirror of
https://github.com/CnTransGroup/EffectiveModernCppChinese.git
synced 2024-10-28 04:00:13 +08:00
Update item18.md
This commit is contained in:
parent
c478c42044
commit
814bebe888
@ -1,53 +1,52 @@
|
||||
# CHAPTER 4 Smart Pointers
|
||||
# 第4章 智能指针
|
||||
|
||||
诗人和歌曲作家喜欢爱。有时候喜欢计数。很少情况下两者兼有。受伊丽莎白·巴雷特·勃朗宁(Elizabeth Barrett Browning)对爱和数的不同看法的启发(“我怎么爱你?”让我数一数。”)和保罗·西蒙(Paul Simon)(“离开你的爱人必须有50种方法。”),我们可以试着枚举一些为什么原始指针很难被爱的原因:
|
||||
**CHAPTER 4 Smart Pointers**
|
||||
|
||||
诗人和歌曲作家喜欢爱。有时候喜欢计数。很少情况下两者兼有。受伊丽莎白·巴雷特·勃朗宁(Elizabeth Barrett Browning)对爱和数的不同看法的启发(“我怎么爱你?让我数一数。”)和保罗·西蒙(Paul Simon)(“离开你的爱人必须有50种方法。”),我们可以试着枚举一些为什么原始指针很难被爱的原因:
|
||||
|
||||
1. 它的声明不能指示所指到底是单个对象还是数组。
|
||||
2. 它的声明没有告诉你用完后是否应该销毁它,即指针是否拥有所指之物。
|
||||
3. 如果你决定你应该销毁对象所指,没人告诉你该用delete还是其他析构机制(比如将指针传给专门的销毁函数)。
|
||||
4. 如果你发现该用delete。 原因1说了不知道是delete单个对象还是delete数组。如果用错了结果是未定义的。
|
||||
5. 假设你确定了指针所指,知道销毁机制,也很难确定你在所有执行路径上都执行了销毁操作(包括异常产生后的路径)。少一条路径就会产生资源泄漏,销毁多次还会导致未定义行为。
|
||||
6. 一般来说没有办法告诉你指针是否变成了悬空指针(dangling pointers),即内存中不再存在指针所指之物。悬空指针会在对象销毁后仍然指向它们。
|
||||
3. 如果你决定你应该销毁指针所指对象,没人告诉你该用`delete`还是其他析构机制(比如将指针传给专门的销毁函数)。
|
||||
4. 如果你发现该用`delete`。 原因1说了可能不知道该用单个对象形式(“`delete`”)还是数组形式(“`delete[]`”)。如果用错了结果是未定义的。
|
||||
5. 假设你确定了指针所指,知道销毁机制,也很难确定你在所有执行路径上都执行了**恰为一次**销毁操作(包括异常产生后的路径)。少一条路径就会产生资源泄漏,销毁多次还会导致未定义行为。
|
||||
6. 一般来说没有办法告诉你指针是否变成了悬空指针(dangling pointers),即内存中不再存在指针所指之物。在对象销毁后指针仍指向它们就会产生悬空指针。
|
||||
|
||||
原始指针是强大的工具,当然,另一方面几十年的经验证明,只要注意力稍有疏忽,这个强大的工具就会攻击它的主人。
|
||||
|
||||
智能指针是解决这些问题的一种办法。智能指针包裹原始指针,它们的行为看起来像被包裹的原始指针,但避免了原始指针的很多陷阱。你应该更倾向于智能指针而不是原始指针。几乎原始指针能做的所有事情智能指针都能做,而且出错的机会更少。
|
||||
**智能指针**(*smart pointers*)是解决这些问题的一种办法。智能指针包裹原始指针,它们的行为看起来像被包裹的原始指针,但避免了原始指针的很多陷阱。你应该更倾向于智能指针而不是原始指针。几乎原始指针能做的所有事情智能指针都能做,而且出错的机会更少。
|
||||
|
||||
在C++11中存在四种智能指针:`std::auto_ptr`, `std::unique_ptr`, `std::shared_ptr`,` std::weak_ptr`。都是被设计用来帮助管理动态对象的生命周期,在适当的时间通过适当的方式来销毁对象,以避免出现资源泄露或者异常行为。
|
||||
在C++11中存在四种智能指针:`std::auto_ptr`,`std::unique_ptr`,`std::shared_ptr`,` std::weak_ptr`。都是被设计用来帮助管理动态对象的生命周期,在适当的时间通过适当的方式来销毁对象,以避免出现资源泄露或者异常行为。
|
||||
|
||||
`std::auto_ptr`是C++98的遗留物,它是一次标准化的尝试,后来变成了C++11的`std::unique_ptr`。要正确的模拟原生指针需要移动语义,但是C++98没有这个东西。取而代之,`std::auto_ptr`拉拢拷贝操作来达到自己的移动意图。这导致了令人奇怪的代码(拷贝一个`std::auto_ptr`会将它本身设置为null!)和令人沮丧的使用限制(比如不能将`std::auto_ptr`放入容器)。
|
||||
`std::auto_ptr`是来自C++98的已废弃遗留物,它是一次标准化的尝试,后来变成了C++11的`std::unique_ptr`。要正确的模拟原生指针需要移动语义,但是C++98没有这个东西。取而代之,`std::auto_ptr`拉拢拷贝操作来达到自己的移动意图。这导致了令人奇怪的代码(拷贝一个`std::auto_ptr`会将它本身设置为null!)和令人沮丧的使用限制(比如不能将`std::auto_ptr`放入容器)。
|
||||
|
||||
`std::unique_ptr`能做`std::auto_ptr`可以做的所有事情以及更多。它能高效完成任务,而且不会扭曲拷贝语义。在所有方面它都比`std::unique_ptr`好。现在`std::auto_ptr`唯一合法的使用场景就是代码使用C++98编译器编译。除非你有上述限制,否则你就该把`std::auto_ptr`替换为`std::unique_ptr`而且绝不回头。
|
||||
`std::unique_ptr`能做`std::auto_ptr`可以做的所有事情以及更多。它能高效完成任务,而且不会扭曲自己的原本含义而变成拷贝对象。在所有方面它都比`std::auto_ptr`好。现在`std::auto_ptr`唯一合法的使用场景就是代码使用C++98编译器编译。除非你有上述限制,否则你就该把`std::auto_ptr`替换为`std::unique_ptr`而且绝不回头。
|
||||
|
||||
各种智能指针的API有极大的不同。唯一功能性相似的可能就是默认构造函数。因为有很多关于这些API的详细手册,所以我将只关注那些API概览没有提及的内容,比如值得注意的使用场景,运行时性能分析等,掌握这些信息可以更高效的使用智能指针。
|
||||
|
||||
|
||||
## Item 18:对于独占资源使用std::unique_ptr
|
||||
当你需要一个智能指针时,`std::unique_ptr`通常是最合适的。可以合理假设,默认情况下,`std::unique_ptr`等同于原始指针,而且对于大多数操作(包括取消引用),他们执行的指令完全相同。这意味着你甚至可以在内存和时间都比较紧张的情况下使用它。如果原始指针够小够快,那么`std::unique_ptr`一样可以。
|
||||
## 条款十八:对于独占资源使用`std::unique_ptr`
|
||||
**Item 18: Use `std::unique_ptr` for exclusive-ownership resource management**
|
||||
|
||||
`std::unique_ptr`体现了专有所有权语义。一个`non-null std::unique_ptr`始终有其指向的内容。移动操作将所有权从源指针转移到目的指针,拷贝操作是不允许的,因为如果你能拷贝一个`std::unique_ptr`,你会得到指向相同内容的两个`std::unique_ptr`,每个都认为自己拥有资源,销毁时就会出现重复销毁。因此,`std::unique_ptr`只支持移动操作。当`std::unique_ptr`销毁时,其指向的资源也执行析构函数。而原始指针需要显示调用delete来销毁指针指向的资源。
|
||||
当你需要一个智能指针时,`std::unique_ptr`通常是最合适的。可以合理假设,默认情况下,`std::unique_ptr`大小等同于原始指针,而且对于大多数操作(包括取消引用),他们执行的指令完全相同。这意味着你甚至可以在内存和时间都比较紧张的情况下使用它。如果原始指针够小够快,那么`std::unique_ptr`一样可以。
|
||||
|
||||
`std::unique_ptr`的常见用法是作为继承层次结构中对象的工厂函数返回类型。假设我们有一个基类Investment(比如 stocks,bonds,real estate等)的继承结构。
|
||||
`std::unique_ptr`体现了专有所有权(*exclusive ownership*)语义。一个non-null `std::unique_ptr`始终拥有其指向的内容。移动一个`std::unique_ptr`将所有权从源指针转移到目的指针。(源指针被设为null。)拷贝一个`std::unique_ptr`是不允许的,因为如果你能拷贝一个`std::unique_ptr`,你会得到指向相同内容的两个`std::unique_ptr`,每个都认为自己拥有(并且应当最后销毁)资源,销毁时就会出现重复销毁。因此,`std::unique_ptr`是一种只可移动类型(*move-only type*)。当析构时,一个non-null `std::unique_ptr`销毁它指向的资源。默认情况下,资源析构通过对`std::unique_ptr`里原始指针调用`delete`来实现。
|
||||
|
||||
`std::unique_ptr`的常见用法是作为继承层次结构中对象的工厂函数返回类型。假设我们有一个investment类型(比如stocks,bonds,real estate等)的继承结构,使用基类`Investment`。
|
||||
|
||||
```cpp
|
||||
class Investment { ... };
|
||||
class Stock: public Investment {...};
|
||||
class Bond: public Investment {...};
|
||||
class RealEstate: public Investment {...};
|
||||
class Investment { … };
|
||||
class Stock: public Investment { … };
|
||||
class Bond: public Investment { … };
|
||||
class RealEstate: public Investment { … };
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
Investment <|-- Stock
|
||||
Investment <|-- Bond
|
||||
Investment <|-- RealEstate
|
||||
```
|
||||
![item18_fig1](media/item18_fig1.png)
|
||||
|
||||
这种继承关系的工厂函数在堆上分配一个对象然后返回指针,调用方在不需要的时候,销毁对象。这使用场景完美匹配`std::unique_ptr`,因为调用者对工厂返回的资源负责(即对该资源的专有所有权),并且`std::unique_ptr`会自动销毁指向的内容。可以这样声明:
|
||||
这种继承关系的工厂函数在堆上分配一个对象然后返回指针,调用方在不需要的时候有责任销毁对象。这使用场景完美匹配`std::unique_ptr`,因为调用者对工厂返回的资源负责(即对该资源的专有所有权),并且`std::unique_ptr`在自己被销毁时会自动销毁指向的内容。`Investment`继承关系的工厂函数可以这样声明:
|
||||
|
||||
```cpp
|
||||
template<typename... Ts>
|
||||
std::unique_ptr<Investment>
|
||||
template<typename... Ts> //返回指向对象的std::unique_ptr,
|
||||
std::unique_ptr<Investment> //对象使用给定实参创建
|
||||
makeInvestment(Ts&&... params);
|
||||
```
|
||||
|
||||
@ -55,136 +54,143 @@ makeInvestment(Ts&&... params);
|
||||
|
||||
```cpp
|
||||
{
|
||||
...
|
||||
auto pInvestment = makeInvestment(arguments);
|
||||
...
|
||||
} //destroy *pInvestment
|
||||
…
|
||||
auto pInvestment = //pInvestment是
|
||||
makeInvestment( arguments ); //std::unique_ptr<Investment>类型
|
||||
…
|
||||
} //销毁 *pInvestment
|
||||
```
|
||||
|
||||
但是也可以在所有权转移的场景中使用它,比如将工厂返回的`std::unique_ptr`移入容器中,然后将容器元素移入对象的数据成员中,然后对象随即被销毁。发生这种情况时,并且销毁该对象将导致销毁从工厂返回的资源,对象`std::unique_ptr`的数据成员也被销毁。如果所有权链由于异常或者其他非典型控制流出现中断(比如提前return函数或者循环中的break),则拥有托管资源的`std::unique_ptr`将保证指向内容的析构函数被调用,销毁对应资源。
|
||||
但是也可以在所有权转移的场景中使用它,比如将工厂返回的`std::unique_ptr`移入容器中,然后将容器元素移入一个对象的数据成员中,然后对象过后被销毁。发生这种情况时,这个对象的`std::unique_ptr`数据成员也被销毁,并且智能指针数据成员的析构将导致销毁从工厂返回的资源。如果所有权链由于异常或者其他非典型控制流出现中断(比如提前从函数return或者循环中的`break`),则拥有托管资源的`std::unique_ptr`将保证指向内容的析构函数被调用,销毁对应资源。(这个规则也有些例外。大多数情况发生于不正常的程序终止。如果一个异常传播离开线程的基本函数(比如程序初始线程的`main`函数),或者违反`noexcept`说明(见[Item14](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item14.md)),局部变量可能不会被销毁;如果`std::abort`或者退出函数(如`std::_Exit`,`std::exit`,或`std::quick_exit`)被调用,局部变量一定没被销毁。)
|
||||
|
||||
默认情况下,销毁将通过delete进行,但是在构造过程中,可以自定义`std::unique_ptr`指向对象的析构函数:任意函数(或者函数对象,包括lambda)。如果通过`makeInvestment`创建的对象不能直接被删除,应该首先写一条日志,可以实现如下:
|
||||
默认情况下,销毁将通过`delete`进行,但是在构造过程中,`std::unique_ptr`对象可以设置使用对资源的**自定义删除器**:当资源需要销毁时可调用的任意函数(或者函数对象,包括*lambda*表达式)。如果通过`makeInvestment`创建的对象不应仅仅被`delete`,而应该先写一条日志,`makeInvestment`可以实现如下。(代码后有说明,别担心有些东西的动机不那么明显。)
|
||||
|
||||
```cpp
|
||||
auto delInvmt = [](Investment* pInvestment)
|
||||
{
|
||||
makeLogEntry(pInvestment);
|
||||
delete pInvestment;
|
||||
};
|
||||
auto delInvmt = [](Investment* pInvestment) //自定义删除器
|
||||
{ //(lambda表达式)
|
||||
makeLogEntry(pInvestment);
|
||||
delete pInvestment;
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
std::unique_ptr<Investment, decltype(delInvmt)>
|
||||
makeInvestment(Ts&& params)
|
||||
std::unique_ptr<Investment, decltype(delInvmt)> //更改后的返回类型
|
||||
makeInvestment(Ts&&... params)
|
||||
{
|
||||
std::unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt);
|
||||
if (/*a Stock object should be created*/)
|
||||
{
|
||||
pInv.reset(new Stock(std::forward<Ts>(params)...));
|
||||
}
|
||||
else if ( /* a Bond object should be created */ )
|
||||
{
|
||||
pInv.reset(new Bond(std::forward<Ts>(params)...));
|
||||
}
|
||||
else if ( /* a RealEstate object should be created */ )
|
||||
{
|
||||
pInv.reset(new RealEstate(std::forward<Ts>(params)...));
|
||||
}
|
||||
return pInv;
|
||||
std::unique_ptr<Investment, decltype(delInvmt)> //应返回的指针
|
||||
pInv(nullptr, delInvmt);
|
||||
if (/*一个Stock对象应被创建*/)
|
||||
{
|
||||
pInv.reset(new Stock(std::forward<Ts>(params)...));
|
||||
}
|
||||
else if ( /*一个Bond对象应被创建*/ )
|
||||
{
|
||||
pInv.reset(new Bond(std::forward<Ts>(params)...));
|
||||
}
|
||||
else if ( /*一个RealEstate对象应被创建*/ )
|
||||
{
|
||||
pInv.reset(new RealEstate(std::forward<Ts>(params)...));
|
||||
}
|
||||
return pInv;
|
||||
}
|
||||
```
|
||||
|
||||
稍后,我将解释其工作原理,但首先请考虑如果你是调用者,情况如何。假设你存储`makeInvestment`调用结果在auto变量中,那么你将在愉快中忽略在删除过程中需要特殊处理的事实,当然,你确实幸福,因为使用了`unique_ptr`意味着你不需要考虑在资源释放时的路径,以及确保只释放一次,`std::unique_ptr`自动解决了这些问题。从使用者角度,`makeInvestment`接口很棒。
|
||||
稍后,我将解释其工作原理,但首先请考虑如果你是调用者,情况如何。假设你存储`makeInvestment`调用结果到`auto`变量中,那么你将在愉快中忽略在删除过程中需要特殊处理的事实。当然,你确实幸福,因为使用了`unique_ptr`意味着你不需要关心什么时候资源应被释放,不需要考虑在资源释放时的路径,以及确保只释放一次,`std::unique_ptr`自动解决了这些问题。从使用者角度,`makeInvestment`接口很棒。
|
||||
|
||||
这个实现确实相当棒,如果你理解了:
|
||||
|
||||
- `delInvmt`是自定义的从`makeInvestment`返回的析构函数。所有的自定义的析构行为接受要销毁对象的原始指针,然后执行销毁操作。如上例子。使用lambda创建`delInvmt`是方便的,而且,正如稍后看到的,比编写常规的函数更有效
|
||||
- `delInvmt`是从`makeInvestment`返回的对象的自定义的删除器。所有的自定义的删除行为接受要销毁对象的原始指针,然后执行所有必要行为实现销毁操作。在上面情况中,操作包括调用`makeLogEntry`然后应用`delete`。使用*lambda*创建`delInvmt`是方便的,而且,正如稍后看到的,比编写常规的函数更有效。
|
||||
|
||||
- 当使用自定义删除器时,必须将其作为第二个参数传给`std::unique_ptr`。对于decltype,更多信息查看Item3
|
||||
- 当使用自定义删除器时,删除器类型必须作为第二个类型实参传给`std::unique_ptr`。在上面情况中,就是`delInvmt`的类型,这就是为什么`makeInvestment`返回类型是`std::unique_ptr<Investment, decltype(delInvmt)>`。(对于`decltype`,更多信息查看[Item3](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/1.DeducingTypes/item3.md))
|
||||
|
||||
- `makeInvestment`的基本策略是创建一个空的`std::unique_ptr`,然后指向一个合适类型的对象,然后返回。为了与pInv关联自定义删除器,作为构造函数的第二个参数
|
||||
- `makeInvestment`的基本策略是创建一个空的`std::unique_ptr`,然后指向一个合适类型的对象,然后返回。为了将自定义删除器`delInvmt`与`pInv`关联,我们把`delInvmt`作为`pInv`构造函数的第二个实参。
|
||||
|
||||
- 尝试将原始指针(比如new创建)赋值给`std::unique_ptr`通不过编译,因为不存在从原始指针到智能指针的隐式转换。这种隐式转换会出问题,所以禁止。这就是为什么通过`reset`来传递new指针的原因
|
||||
- 尝试将原始指针(比如`new`创建)赋值给`std::unique_ptr`通不过编译,因为是一种从原始指针到智能指针的隐式转换。这种隐式转换会出问题,所以C++11的智能指针禁止这个行为。这就是通过`reset`来让`pInv`接管通过`new`创建的对象的所有权的原因。
|
||||
|
||||
- 使用new时,要使用`std::forward`作为参数来完美转发给`makeInvestment`(查看Item 25)。这使调用者提供的所有信息可用于正在创建的对象的构造函数
|
||||
- 使用`new`时,我们使用`std::forward`把传给`makeInvestment`的实参完美转发出去(查看[Item25](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/5.RRefMovSemPerfForw/item25.md))。这使调用者提供的所有信息可用于正在创建的对象的构造函数。
|
||||
|
||||
- 自定义删除器的参数类型是`Investment*`,尽管真实的对象类型是在`makeInvestment`内部创建的,它最终通过在lambda表达式中,作为`Investment*`对象被删除。这意味着我们通过基类指针删除派生类实例,为此,基类必须是虚函数析构:
|
||||
- 自定义删除器的一个形参,类型是`Investment*`,不管在`makeInvestment`内部创建的对象的真实类型(如`Stock`,`Bond`,或`RealEstate`)是什么,它最终在*lambda*表达式中,作为`Investment*`对象被删除。这意味着我们通过基类指针删除派生类实例,为此,基类`Investment`必须有虚析构函数:
|
||||
|
||||
```cpp
|
||||
class Investment {
|
||||
public:
|
||||
...
|
||||
virtual ~Investment();
|
||||
...
|
||||
public:
|
||||
…
|
||||
virtual ~Investment(); //关键设计部分!
|
||||
…
|
||||
};
|
||||
```
|
||||
|
||||
在C++14中,函数的返回类型推导存在(参阅Item 3),意味着`makeInvestment`可以更简单,封装的方式实现:
|
||||
在C++14中,函数的返回类型推导存在(参阅[Item3](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/1.DeducingTypes/item3.md)),意味着`makeInvestment`可以以更简单,更封装的方式实现:
|
||||
|
||||
```cpp
|
||||
template<typename... Ts>
|
||||
auto makeInvestment(Ts&& params)
|
||||
auto makeInvestment(Ts&&... params) //C++14
|
||||
{
|
||||
auto delInvmt = [](Investment* pInvestment)
|
||||
{
|
||||
makeLogEntry(pInvestment);
|
||||
delete pInvestment;
|
||||
};
|
||||
std::unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt);
|
||||
if (/*a Stock object should be created*/)
|
||||
{
|
||||
pInv.reset(new Stock(std::forward<Ts>(params)...));
|
||||
}
|
||||
else if ( /* a Bond object should be created */ )
|
||||
{
|
||||
pInv.reset(new Bond(std::forward<Ts>(params)...));
|
||||
}
|
||||
else if ( /* a RealEstate object should be created */ )
|
||||
{
|
||||
pInv.reset(new RealEstate(std::forward<Ts>(params)...));
|
||||
}
|
||||
return pInv;
|
||||
auto delInvmt = [](Investment* pInvestment) //现在在
|
||||
{ //makeInvestment里
|
||||
makeLogEntry(pInvestment);
|
||||
delete pInvestment;
|
||||
};
|
||||
|
||||
std::unique_ptr<Investment, decltype(delInvmt)> //同之前一样
|
||||
pInv(nullptr, delInvmt);
|
||||
if ( … ) //同之前一样
|
||||
{
|
||||
pInv.reset(new Stock(std::forward<Ts>(params)...));
|
||||
}
|
||||
else if ( … ) //同之前一样
|
||||
{
|
||||
pInv.reset(new Bond(std::forward<Ts>(params)...));
|
||||
}
|
||||
else if ( … ) //同之前一样
|
||||
{
|
||||
pInv.reset(new RealEstate(std::forward<Ts>(params)...));
|
||||
}
|
||||
return pInv; //同之前一样
|
||||
}
|
||||
```
|
||||
|
||||
我之前说过,当使用默认删除器时,你可以合理假设`std::unique_ptr`和原始指针大小相同。当自定义删除器时,情况可能不再如此。删除器是个函数指针,通常会使`std::unique_ptr`的字节从一个增加到两个。对于删除器的函数对象来说,大小取决于函数对象中存储的状态多少,无状态函数对象(比如没有捕获的lambda表达式)对大小没有影响,这意味当自定义删除器可以被lambda实现时,尽量使用lambda
|
||||
我之前说过,当使用默认删除器时(如`delete`),你可以合理假设`std::unique_ptr`对象和原始指针大小相同。当自定义删除器时,情况可能不再如此。函数指针形式的删除器,通常会使`std::unique_ptr`的从一个字(*word*)大小增加到两个。对于函数对象形式的删除器来说,变化的大小取决于函数对象中存储的状态多少,无状态函数(stateless function)对象(比如不捕获变量的*lambda*表达式)对大小没有影响,这意味当自定义删除器可以实现为函数或者*lambda*时,尽量使用*lambda*:
|
||||
|
||||
```cpp
|
||||
auto delInvmt = [](Investment* pInvestment)
|
||||
{
|
||||
makeLogEntry(pInvestment);
|
||||
delete pInvestment;
|
||||
};
|
||||
template<typename... Ts>
|
||||
std::unique_ptr<Investment, decltype(delInvmt)>
|
||||
makeInvestment(Ts&& params); //返回Investment*的大小
|
||||
auto delInvmt1 = [](Investment* pInvestment) //无状态lambda的
|
||||
{ //自定义删除器
|
||||
makeLogEntry(pInvestment);
|
||||
delete pInvestment;
|
||||
};
|
||||
|
||||
void delInvmt2(Investment* pInvestment)
|
||||
{
|
||||
makeLogEntry(pInvestment);
|
||||
delete pInvestment;
|
||||
template<typename... Ts> //返回类型大小是
|
||||
std::unique_ptr<Investment, decltype(delInvmt1)> //Investment*的大小
|
||||
makeInvestment(Ts&&... args);
|
||||
|
||||
void delInvmt2(Investment* pInvestment) //函数形式的
|
||||
{ //自定义删除器
|
||||
makeLogEntry(pInvestment);
|
||||
delete pInvestment;
|
||||
}
|
||||
template<typename... Ts>
|
||||
std::unique_ptr<Investment, void(*)(Investment*)>
|
||||
makeInvestment(Ts&&... params); //返回Investment*的指针加至少一个函数指针的大小
|
||||
template<typename... Ts> //返回类型大小是
|
||||
std::unique_ptr<Investment, void (*)(Investment*)> //Investment*的指针
|
||||
makeInvestment(Ts&&... params); //加至少一个函数指针的大小
|
||||
```
|
||||
|
||||
具有很多状态的自定义删除器会产生大尺寸`std::unique_ptr`对象。如果你发现自定义删除器使得你的`std::unique_ptr`变得过大,你需要审视修改你的设计。
|
||||
|
||||
工厂函数不是`std::unique_ptr`的唯一常见用法。作为实现**Pimpl Idiom**的一种机制,它更为流行。代码并不复杂,但是在某些情况下并不直观,所以这安排在Item22的专门主题中。
|
||||
工厂函数不是`std::unique_ptr`的唯一常见用法。作为实现**Pimpl Idiom**(译注:*pointer to implementation*,一种隐藏实际实现而减弱编译依赖性的设计思想,《Effective C++》条款31对此有过叙述)的一种机制,它更为流行。代码并不复杂,但是在某些情况下并不直观,所以这安排在[Item22](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/4.SmartPointers/item22.md)的专门主题中。
|
||||
|
||||
`std::unique_ptr`有两种形式,一种用于单个对象(`std::unique_ptr<T>`),一种用于数组(`std::unique_ptr<T[]>`)。结果就是,指向哪种形式没有歧义。`std::unique_ptr`的API设计会自动匹配你的用法,比如[]操作符就是数组对象,\*和->就是单个对象专有。
|
||||
`std::unique_ptr`有两种形式,一种用于单个对象(`std::unique_ptr<T>`),一种用于数组(`std::unique_ptr<T[]>`)。结果就是,指向哪种形式没有歧义。`std::unique_ptr`的API设计会自动匹配你的用法,比如`operator[]`就是数组对象,解引用操作符(`operator*`和`operator->`)就是单个对象专有。
|
||||
|
||||
数组的`std::unique_ptr`的存在应该不被使用,因为`std::array`, `std::vector`, `std::string`这些更好用的数据容器应该取代原始数组。原始数组的使用唯一情况是你使用类似C的API返回一个指向堆数组的原始指针。
|
||||
你应该对数组的`std::unique_ptr`的存在兴趣泛泛,因为`std::array`,`std::vector`,`std::string`这些更好用的数据容器应该取代原始数组。`std::unique_ptr<T[]>`有用的唯一情况是你使用类似C的API返回一个指向堆数组的原始指针,而你想接管这个数组的所有权。
|
||||
|
||||
`std::unique_ptr`是C++11中表示专有所有权的方法,但是其最吸引人的功能之一是它可以轻松高效的转换为`std::shared_ptr`:
|
||||
|
||||
```cpp
|
||||
std::shared_ptr<Investment> sp = makeInvestment(arguments);
|
||||
std::shared_ptr<Investment> sp = //将std::unique_ptr
|
||||
makeInvestment(arguments); //转为std::shared_ptr
|
||||
```
|
||||
|
||||
这就是为什么`std::unique_ptr`非常适合用作工厂函数返回类型的关键部分。 工厂函数无法知道调用者是否要对它们返回的对象使用专有所有权语义,或者共享所有权(即`std::shared_ptr`)是否更合适。 通过返回`std::unique_ptr`,工厂为调用者提供了最有效的智能指针,但它们并不妨碍调用者用其更灵活的兄弟替换它。(有关`std::shared_ptr`的信息,请转到Item 19。
|
||||
这就是`std::unique_ptr`非常适合用作工厂函数返回类型的原因的关键部分。 工厂函数无法知道调用者是否要对它们返回的对象使用专有所有权语义,或者共享所有权(即`std::shared_ptr`)是否更合适。 通过返回`std::unique_ptr`,工厂为调用者提供了最有效的智能指针,但它们并不妨碍调用者用其更灵活的兄弟替换它。(有关`std::shared_ptr`的信息,请转到[Item19](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/4.SmartPointers/item19.md)。)
|
||||
|
||||
### 小结
|
||||
**请记住:**
|
||||
|
||||
- `std::unique_ptr`是轻量级、快速的、只能move的管理专有所有权语义资源的智能指针
|
||||
- 默认情况,资源销毁通过delete,但是支持自定义delete函数。有状态的删除器和函数指针会增加`std::unique_ptr`的大小
|
||||
- 将`std::unique_ptr`转化为`std::shared_ptr`是简单的
|
||||
- `std::unique_ptr`是轻量级、快速的、只可移动(*move-only*)的管理专有所有权语义资源的智能指针
|
||||
- 默认情况,资源销毁通过`delete`实现,但是支持自定义删除器。有状态的删除器和函数指针会增加`std::unique_ptr`对象的大小
|
||||
- 将`std::unique_ptr`转化为`std::shared_ptr`非常简单
|
||||
|
Loading…
Reference in New Issue
Block a user