This commit is contained in:
kelthuzadx 2022-11-19 14:13:45 +00:00
parent 0466a061bc
commit 266cceb3e4
4 changed files with 10 additions and 10 deletions

View File

@ -225,7 +225,7 @@ Widget w; //错误!
<p>在Pimpl惯用法中使用<code>std::unique_ptr</code>会抛出错误,有点惊悚,因为第一<code>std::unique_ptr</code>宣称它支持未完成类型第二Pimpl惯用法是<code>std::unique_ptr</code>的最常见的使用情况之一。 幸运的是,让这段代码能正常运行很简单。 只需要对上面出现的问题的原因有一个基础的认识就可以了。</p>
<p>在对象<code>w</code>被析构时(例如离开了作用域),问题出现了。在这个时候,它的析构函数被调用。我们在类的定义里使用了<code>std::unique_ptr</code>,所以我们没有声明一个析构函数,因为我们并没有任何代码需要写在里面。根据编译器自动生成的特殊成员函数的规则(见 <a href="https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item17.md">Item17</a>),编译器会自动为我们生成一个析构函数。 在这个析构函数里,编译器会插入一些代码来调用类<code>Widget</code>的数据成员<code>pImpl</code>的析构函数。 <code>pImpl</code>是一个<code>std::unique_ptr&lt;Widget::Impl&gt;</code>,也就是说,一个使用默认删除器的<code>std::unique_ptr</code>。 默认删除器是一个函数,它使用<code>delete</code>来销毁内置于<code>std::unique_ptr</code>的原始指针。然而,在使用<code>delete</code>之前通常会使默认删除器使用C++11的特性<code>static_assert</code>来确保原始指针指向的类型不是一个未完成类型。 当编译器为<code>Widget w</code>的析构生成代码时,它会遇到<code>static_assert</code>检查并且失败,这通常是错误信息的来源。 这些错误信息只在对象<code>w</code>销毁的地方出现,因为类<code>Widget</code>的析构函数,正如其他的编译器生成的特殊成员函数一样,是暗含<code>inline</code>属性的。 错误信息自身往往指向对象<code>w</code>被创建的那行,因为这行代码明确地构造了这个对象,导致了后面潜在的析构。</p>
<p>为了解决这个问题,你只需要确保在编译器生成销毁<code>std::unique_ptr&lt;Widget::Impl&gt;</code>的代码之前, <code>Widget::Impl</code>已经是一个完成类型(<em>complete type</em>)。 当编译器“看到”它的定义的时候,该类型就成为完成类型了。 但是 <code>Widget::Impl</code>的定义在<code>widget.cpp</code>里。成功编译的关键,就是在<code>widget.cpp</code>文件内,让编译器在“看到” <code>Widget</code>的析构函数实现之前(也即编译器插入的,用来销毁<code>std::unique_ptr</code>这个数据成员的代码的,那个位置),先定义<code>Widget::Impl</code></p>
<p>做出这样的调整很容易。只需要先在<code>widget.h</code>里,只声明类<code>Widget</code>的析构函数,不要在这里定义它:</p>
<p>做出这样的调整很容易。只需要先在<code>widget.h</code>里,只声明类<code>Widget</code>的析构函数,不要在这里定义它:</p>
<pre><code class="language-cpp">class Widget { //跟之前一样在“widget.h”中
public:
Widget();
@ -256,10 +256,10 @@ Widget::Widget() //跟之前一样
Widget::~Widget() //析构函数的定义(译者注:这里高亮)
{}
</code></pre>
<p>这样就可以了,并且这样增加的代码也最少,但是,如果你想要强调编译器自动生成的析构函数做的没错——你声明<code>Widget</code>的析构函数的唯一原因,是确保它会在<code>Widget</code>的实现文件内(译者注:指<code>widget.cpp</code>被自动生成,你可以把析构函数体直接定义为<code>= default</code></p>
<p>这样就可以了,并且这样增加的代码也最少,你声明<code>Widget</code>析构函数只是为了在 Widget 的实现文件中(译者注:指<code>widget.cpp</code>写出它的定义,但是如果你想强调编译器自动生成的析构函数会做和你一样正确的事情,你可以直接使用<code>= default</code>定义析构函数体</p>
<pre><code class="language-cpp">Widget::~Widget() = default; //同上述代码效果一致
</code></pre>
<p>使用了Pimpl惯用法的类自然适合支持移动操作因为编译器自动生成的移动操作正合我们所意对其中的<code>std::unique_ptr</code>进行移动。 正如<a href="https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item17.md">Item17</a>所解释的那样,声明一个类<code>Widget</code>的析构函数会阻止编译器生成移动操作,所以如果你想要支持移动操作,你必须自己声明相关的函数。考虑到编译器自动生成的版本能够正常功能,你可能会被诱使着来这样实现:</p>
<p>使用了Pimpl惯用法的类自然适合支持移动操作因为编译器自动生成的移动操作正合我们所意对其中的<code>std::unique_ptr</code>进行移动。 正如<a href="https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item17.md">Item17</a>所解释的那样,声明一个类<code>Widget</code>的析构函数会阻止编译器生成移动操作,所以如果你想要支持移动操作,你必须自己声明相关的函数。考虑到编译器自动生成的版本会正常运行,你可能会很想按如下方式实现它们:</p>
<pre><code class="language-cpp">class Widget { //仍然在“widget.h”中
public:
Widget();
@ -304,7 +304,7 @@ Widget::~Widget() = default; //跟之前一样
Widget::Widget(Widget&amp;&amp; rhs) = default; //这里定义
Widget&amp; Widget::operator=(Widget&amp;&amp; rhs) = default;
</code></pre>
<p>Pimpl惯用法是用来减少类的实现和类使用者之间的编译依赖的一种方法但是从概念而言使用这种惯用法并不改变这个类的表现。 原来的类<code>Widget</code>包含有<code>std::string</code><code>std::vector</code><code>Gadget</code>数据成员,并且,假设类型<code>Gadget</code>,如同<code>std::string</code><code>std::vector</code>一样,允许复制操作,所以类<code>Widget</code>支持复制操作也很合理。 我们必须要自己来写这些函数,因为第一,对包含有只可移动(<em>move-only</em>)类型,如<code>std::unique_ptr</code>的类,编译器不会生成复制操作;第二,即使编译器帮我们生成了,生成的复制操作也只会复制<code>std::unique_ptr</code>(也即浅复制<em>shallow copy</em>,而实际上我们需要复制指针所指向的对象(也即深复制<em>deep copy</em>))。</p>
<p>Pimpl惯用法是用来减少类的实现和类使用者之间的编译依赖的一种方法但是从概念而言使用这种惯用法并不改变这个类的表现。 原来的类<code>Widget</code>包含有<code>std::string</code><code>std::vector</code><code>Gadget</code>数据成员,并且,假设类型<code>Gadget</code>,如同<code>std::string</code><code>std::vector</code>一样,允许复制操作,所以类<code>Widget</code>支持复制操作也很合理。 我们必须要自己来写这些函数,因为第一,对包含有只可移动(<em>move-only</em>)类型,如<code>std::unique_ptr</code>的类,编译器不会生成复制操作;第二,即使编译器帮我们生成了,生成的复制操作也只会复制<code>std::unique_ptr</code>(也即浅拷贝<em>shallow copy</em>,而实际上我们需要复制指针所指向的对象(也即深拷贝<em>deep copy</em>))。</p>
<p>使用我们已经熟悉的方法,我们在头文件里声明函数,而在实现文件里去实现他们:</p>
<pre><code class="language-cpp">class Widget { //仍然在“widget.h”中
public:

View File

@ -3085,7 +3085,7 @@ Widget w; //错误!
<p>在Pimpl惯用法中使用<code>std::unique_ptr</code>会抛出错误,有点惊悚,因为第一<code>std::unique_ptr</code>宣称它支持未完成类型第二Pimpl惯用法是<code>std::unique_ptr</code>的最常见的使用情况之一。 幸运的是,让这段代码能正常运行很简单。 只需要对上面出现的问题的原因有一个基础的认识就可以了。</p>
<p>在对象<code>w</code>被析构时(例如离开了作用域),问题出现了。在这个时候,它的析构函数被调用。我们在类的定义里使用了<code>std::unique_ptr</code>,所以我们没有声明一个析构函数,因为我们并没有任何代码需要写在里面。根据编译器自动生成的特殊成员函数的规则(见 <a href="https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item17.md">Item17</a>),编译器会自动为我们生成一个析构函数。 在这个析构函数里,编译器会插入一些代码来调用类<code>Widget</code>的数据成员<code>pImpl</code>的析构函数。 <code>pImpl</code>是一个<code>std::unique_ptr&lt;Widget::Impl&gt;</code>,也就是说,一个使用默认删除器的<code>std::unique_ptr</code>。 默认删除器是一个函数,它使用<code>delete</code>来销毁内置于<code>std::unique_ptr</code>的原始指针。然而,在使用<code>delete</code>之前通常会使默认删除器使用C++11的特性<code>static_assert</code>来确保原始指针指向的类型不是一个未完成类型。 当编译器为<code>Widget w</code>的析构生成代码时,它会遇到<code>static_assert</code>检查并且失败,这通常是错误信息的来源。 这些错误信息只在对象<code>w</code>销毁的地方出现,因为类<code>Widget</code>的析构函数,正如其他的编译器生成的特殊成员函数一样,是暗含<code>inline</code>属性的。 错误信息自身往往指向对象<code>w</code>被创建的那行,因为这行代码明确地构造了这个对象,导致了后面潜在的析构。</p>
<p>为了解决这个问题,你只需要确保在编译器生成销毁<code>std::unique_ptr&lt;Widget::Impl&gt;</code>的代码之前, <code>Widget::Impl</code>已经是一个完成类型(<em>complete type</em>)。 当编译器“看到”它的定义的时候,该类型就成为完成类型了。 但是 <code>Widget::Impl</code>的定义在<code>widget.cpp</code>里。成功编译的关键,就是在<code>widget.cpp</code>文件内,让编译器在“看到” <code>Widget</code>的析构函数实现之前(也即编译器插入的,用来销毁<code>std::unique_ptr</code>这个数据成员的代码的,那个位置),先定义<code>Widget::Impl</code></p>
<p>做出这样的调整很容易。只需要先在<code>widget.h</code>里,只声明类<code>Widget</code>的析构函数,不要在这里定义它:</p>
<p>做出这样的调整很容易。只需要先在<code>widget.h</code>里,只声明类<code>Widget</code>的析构函数,不要在这里定义它:</p>
<pre><code class="language-cpp">class Widget { //跟之前一样在“widget.h”中
public:
Widget();
@ -3116,10 +3116,10 @@ Widget::Widget() //跟之前一样
Widget::~Widget() //析构函数的定义(译者注:这里高亮)
{}
</code></pre>
<p>这样就可以了,并且这样增加的代码也最少,但是,如果你想要强调编译器自动生成的析构函数做的没错——你声明<code>Widget</code>的析构函数的唯一原因,是确保它会在<code>Widget</code>的实现文件内(译者注:指<code>widget.cpp</code>被自动生成,你可以把析构函数体直接定义为<code>= default</code></p>
<p>这样就可以了,并且这样增加的代码也最少,你声明<code>Widget</code>析构函数只是为了在 Widget 的实现文件中(译者注:指<code>widget.cpp</code>写出它的定义,但是如果你想强调编译器自动生成的析构函数会做和你一样正确的事情,你可以直接使用<code>= default</code>定义析构函数体</p>
<pre><code class="language-cpp">Widget::~Widget() = default; //同上述代码效果一致
</code></pre>
<p>使用了Pimpl惯用法的类自然适合支持移动操作因为编译器自动生成的移动操作正合我们所意对其中的<code>std::unique_ptr</code>进行移动。 正如<a href="https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item17.md">Item17</a>所解释的那样,声明一个类<code>Widget</code>的析构函数会阻止编译器生成移动操作,所以如果你想要支持移动操作,你必须自己声明相关的函数。考虑到编译器自动生成的版本能够正常功能,你可能会被诱使着来这样实现:</p>
<p>使用了Pimpl惯用法的类自然适合支持移动操作因为编译器自动生成的移动操作正合我们所意对其中的<code>std::unique_ptr</code>进行移动。 正如<a href="https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/3.MovingToModernCpp/item17.md">Item17</a>所解释的那样,声明一个类<code>Widget</code>的析构函数会阻止编译器生成移动操作,所以如果你想要支持移动操作,你必须自己声明相关的函数。考虑到编译器自动生成的版本会正常运行,你可能会很想按如下方式实现它们:</p>
<pre><code class="language-cpp">class Widget { //仍然在“widget.h”中
public:
Widget();
@ -3164,7 +3164,7 @@ Widget::~Widget() = default; //跟之前一样
Widget::Widget(Widget&amp;&amp; rhs) = default; //这里定义
Widget&amp; Widget::operator=(Widget&amp;&amp; rhs) = default;
</code></pre>
<p>Pimpl惯用法是用来减少类的实现和类使用者之间的编译依赖的一种方法但是从概念而言使用这种惯用法并不改变这个类的表现。 原来的类<code>Widget</code>包含有<code>std::string</code><code>std::vector</code><code>Gadget</code>数据成员,并且,假设类型<code>Gadget</code>,如同<code>std::string</code><code>std::vector</code>一样,允许复制操作,所以类<code>Widget</code>支持复制操作也很合理。 我们必须要自己来写这些函数,因为第一,对包含有只可移动(<em>move-only</em>)类型,如<code>std::unique_ptr</code>的类,编译器不会生成复制操作;第二,即使编译器帮我们生成了,生成的复制操作也只会复制<code>std::unique_ptr</code>(也即浅复制<em>shallow copy</em>,而实际上我们需要复制指针所指向的对象(也即深复制<em>deep copy</em>))。</p>
<p>Pimpl惯用法是用来减少类的实现和类使用者之间的编译依赖的一种方法但是从概念而言使用这种惯用法并不改变这个类的表现。 原来的类<code>Widget</code>包含有<code>std::string</code><code>std::vector</code><code>Gadget</code>数据成员,并且,假设类型<code>Gadget</code>,如同<code>std::string</code><code>std::vector</code>一样,允许复制操作,所以类<code>Widget</code>支持复制操作也很合理。 我们必须要自己来写这些函数,因为第一,对包含有只可移动(<em>move-only</em>)类型,如<code>std::unique_ptr</code>的类,编译器不会生成复制操作;第二,即使编译器帮我们生成了,生成的复制操作也只会复制<code>std::unique_ptr</code>(也即浅拷贝<em>shallow copy</em>,而实际上我们需要复制指针所指向的对象(也即深拷贝<em>deep copy</em>))。</p>
<p>使用我们已经熟悉的方法,我们在头文件里声明函数,而在实现文件里去实现他们:</p>
<pre><code class="language-cpp">class Widget { //仍然在“widget.h”中
public:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long