EffectiveModernCppChinese/3.MovingToModernCpp/item11.html

287 lines
27 KiB
HTML
Raw Normal View History

<!DOCTYPE HTML>
<html lang="zh" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
2022-07-02 13:21:48 +08:00
<title>Item 11:优先考虑使用deleted函数而非使用未定义的私有声明 - Effective Modern C++</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- MathJax -->
<script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="../Introduction.html">简介</a></li><li class="chapter-item expanded "><div>第一章 类型推导</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../1.DeducingTypes/item1.html">Item 1:理解模板类型推导</a></li><li class="chapter-item expanded "><a href="../1.DeducingTypes/item2.html">Item 2:理解auto类型推导</a></li><li class="chapter-item expanded "><a href="../1.DeducingTypes/item3.html">Item 3:理解decltype</a></li><li class="chapter-item expanded "><a href="../1.DeducingTypes/item4.html">Item 4:学会查看类型推导结果</a></li></ol></li><li class="chapter-item expanded "><div>第二章 auto</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../2.Auto/item5.html">Item 5:优先考虑auto而非显式类型声明</a></li><li class="chapter-item expanded "><a href="../2.Auto/item6.html">Item 6:auto推导若非己愿使用显式类型初始化惯用法</a></li></ol></li><li class="chapter-item expanded "><div>第三章 移步现代C++</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item7.html">Item 7:区别使用()和{}创建对象</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item8.html">Item 8:优先考虑nullptr而非0和NULL</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item9.html">Item 9:优先考虑别名声明而非typedefs</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item10.html">Item 10:优先考虑限域枚举而非未限域枚举</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item11.html" class="active">Item 11:优先考虑使用deleted函数而非使用未定义的私有声明</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item12.html">Item 12:使用override声明重载函数</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item13.html">Item 13:优先考虑const_iterator而非iterator</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item14.html">Item 14:如果函数不抛出异常请使用noexcept</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item15.html">Item 15:尽可能的使用constexpr</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item16.html">Item 16:让const成员函数线程安全</a></li><li class="chapter-item expanded "><a href="../3.MovingToModernCpp/item17.html">Item 17:理解特殊成员函数函数的生成</a></li></ol></li><li class="chapter-item expanded "><div>第四章 智能指针</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../4.SmartPointers/item18.html">Item 18:对于独占资源使用std::unique_ptr</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item19.html">Item 19:对于共享资源使用std::shared_ptr</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item20.html">Item 20:当std::shared_ptr可能悬空时使用std::weak_ptr</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item21.html">Item 21:优先考虑使用std::make_unique和std::make_shared而非new</a></li><li class="chapter-item expanded "><a href="../4.SmartPointers/item22.html">Item 22:当使用Pimpl惯用法请在实现文件中定义特殊成员函数</a></li></ol></li><li class="chapter-item expanded "><div>第五章 右值引用,移动语义,完美转发</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item23.html">Item 23:理解std::move和std::forward</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item24.html">Item 24:区别通用引用和右值引用</a></li><li class="chapter-item expanded "><a href="../5.RRefMovSemPerfForw/item25.html">Item 25:对于右值引用使用std::move对于通用引用使用std::forward</a></li><li class="chapter-item expanded "><a href="../5
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Effective Modern C++</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/CnTransGroup/EffectiveModernCppChinese" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h2 id="条款十一优先考虑使用deleted函数而非使用未定义的私有声明"><a class="header" href="#条款十一优先考虑使用deleted函数而非使用未定义的私有声明">条款十一:优先考虑使用<em>deleted</em>函数而非使用未定义的私有声明</a></h2>
<p><strong>Item 11: Prefer deleted functions to private undefined ones.</strong></p>
<p>如果你写的代码要被其他人使用你不想让他们调用某个特殊的函数你通常不会声明这个函数。无声明不函数。简简单单但有时C++会给你自动声明一些函数,如果你想防止客户调用这些函数,事情就不那么简单了。</p>
<p>上述场景见于特殊的成员函数即当有必要时C++自动生成的那些函数。<a href="../3.MovingToModernCpp/item17.html">Item17</a>详细讨论了这些函数但是现在我们只关心拷贝构造函数和拷贝赋值运算符重载。本节主要致力于讨论C++98中那些被C++11所取代的最佳实践而且在C++98中你想要禁止使用的成员函数几乎总是拷贝构造函数或者赋值运算符或者两者都是。</p>
<p>在C++98中防止调用这些函数的方法是将它们声明为私有<code>private</code>成员函数并且不定义。举个例子在C++ 标准库<em>iostream</em>继承链的顶部是模板类<code>basic_ios</code>。所有<em>istream</em><em>ostream</em>类都继承此类(直接或者间接)。拷贝<em>istream</em><em>ostream</em>是不合适的,因为这些操作应该怎么做是模棱两可的。比如一个<code>istream</code>对象,代表一个输入值的流,流中有一些已经被读取,有一些可能马上要被读取。如果一个<em>istream</em>被拷贝,需要拷贝将要被读取的值和已经被读取的值吗?解决这个问题最好的方法是不定义这个操作。直接禁止拷贝流。</p>
<p>要使这些<em>istream</em><em>ostream</em>类不可拷贝,<code>basic_ios</code>在C++98中是这样声明的包括注释</p>
<pre><code class="language-cpp">template &lt;class charT, class traits = char_traits&lt;charT&gt; &gt;
class basic_ios : public ios_base {
public:
private:
basic_ios(const basic_ios&amp; ); // not defined
basic_ios&amp; operator=(const basic_ios&amp;); // not defined
};
</code></pre>
<p>将它们声明为私有成员可以防止客户端调用这些函数。故意不定义它们意味着假如还是有代码用它们(比如成员函数或者类的友元<code>friend</code>),就会在链接时引发缺少函数定义(<em>missing function definitions</em>)错误。</p>
<p>在C++11中有一种更好的方式达到相同目的用“<code>= delete</code>”将拷贝构造函数和拷贝赋值运算符标记为<strong><em>deleted</em>函数</strong>译注一些文献翻译为“删除的函数”。上面相同的代码在C++11中是这样声明的</p>
<pre><code class="language-cpp">template &lt;class charT, class traits = char_traits&lt;charT&gt; &gt;
class basic_ios : public ios_base {
public:
basic_ios(const basic_ios&amp; ) = delete;
basic_ios&amp; operator=(const basic_ios&amp;) = delete;
};
</code></pre>
<p>删除这些函数(译注:添加&quot;<code>= delete</code>&quot;)和声明为私有成员可能看起来只是方式不同,别无其他区别。其实还有一些实质性意义。<em>deleted</em>函数不能以任何方式被调用,即使你在成员函数或者友元函数里面调用<em>deleted</em>函数也不能通过编译。这是较之C++98行为的一个改进C++98中不正确的使用这些函数在链接时才被诊断出来。</p>
<p>通常,<em>deleted</em>函数被声明为<code>public</code>而不是<code>private</code>。这也是有原因的。当客户端代码试图调用成员函数C++会在检查<em>deleted</em>状态前检查它的访问性。当客户端代码调用一个私有的<em>deleted</em>函数,一些编译器只会给出该函数是<code>private</code>的错误(译注:而没有诸如该函数被<em>deleted</em>修饰的错误),即使函数的访问性不影响它是否能被使用。所以值得牢记,如果要将老代码的“私有且未定义”函数替换为<em>deleted</em>函数时请一并修改它的访问性为<code>public</code>,这样可以让编译器产生更好的错误信息。</p>
<p><em>deleted</em>函数还有一个重要的优势是<strong>任何</strong>函数都可以标记为<em>deleted</em>,而只有成员函数可被标记为<code>private</code>。(译注:从下文可知“任何”是包含普通函数和成员函数等所有可声明函数的地方,而<code>private</code>方法只适用于成员函数)假如我们有一个非成员函数,它接受一个整型参数,检查它是否为幸运数:</p>
<pre><code class="language-cpp">bool isLucky(int number);
</code></pre>
<p>C++有沉重的C包袱使得含糊的、能被视作数值的任何类型都能隐式转换为<code>int</code>,但是有一些调用可能是没有意义的:</p>
<pre><code class="language-cpp">if (isLucky('a')) … //字符'a'是幸运数?
if (isLucky(true)) … //&quot;true&quot;是?
if (isLucky(3.5)) … //难道判断它的幸运之前还要先截尾成3
</code></pre>
<p>如果幸运数必须真的是整型,我们该禁止这些调用通过编译。</p>
<p>其中一种方法就是创建<em>deleted</em>重载函数,其参数就是我们想要过滤的类型:</p>
<pre><code class="language-cpp">bool isLucky(int number); //原始版本
bool isLucky(char) = delete; //拒绝char
bool isLucky(bool) = delete; //拒绝bool
bool isLucky(double) = delete; //拒绝float和double
</code></pre>
<p>(上面<code>double</code>重载版本的注释说拒绝<code>float</code><code>double</code>可能会让你惊讶,但是请回想一下:将<code>float</code>转换为<code>int</code><code>double</code>C++更喜欢转换为<code>double</code>。使用<code>float</code>调用<code>isLucky</code>因此会调用<code>double</code>重载版本,而不是<code>int</code>版本。好吧,它也会那么去尝试。事实是调用被删除的<code>double</code>重载版本不能通过编译。不再惊讶了吧。)</p>
<p>虽然<em>deleted</em>函数不能被使用,但它们还是存在于你的程序中。也即是说,重载决议会考虑它们。这也是为什么上面的函数声明导致编译器拒绝一些不合适的函数调用。</p>
<pre><code class="language-cpp">if (isLucky('a')) … //错误调用deleted函数
if (isLucky(true)) … //错误!
if (isLucky(3.5f)) … //错误!
</code></pre>
<p>另一个<em>deleted</em>函数用武之地(<code>private</code>成员函数做不到的地方)是禁止一些模板的实例化。假如你要求一个模板仅支持原生指针(尽管<a href="../4.SmartPointers/item18.html">第四章</a>建议使用智能指针代替原生指针):</p>
<pre><code class="language-cpp">template&lt;typename T&gt;
void processPointer(T* ptr);
</code></pre>
<p>在指针的世界里有两种特殊情况。一是<code>void*</code>指针,因为没办法对它们进行解引用,或者加加减减等。另一种指针是<code>char*</code>因为它们通常代表C风格的字符串而不是正常意义下指向单个字符的指针。这两种情况要特殊处理<code>processPointer</code>模板里面,我们假设正确的函数应该拒绝这些类型。也即是说,<code>processPointer</code>不能被<code>void*</code><code>char*</code>调用。</p>
<p>要想确保这个很容易,使用<code>delete</code>标注模板实例:</p>
<pre><code class="language-cpp">template&lt;&gt;
void processPointer&lt;void&gt;(void*) = delete;
template&lt;&gt;
void processPointer&lt;char&gt;(char*) = delete;
</code></pre>
<p>现在如果使用<code>void*</code><code>char*</code>调用<code>processPointer</code>就是无效的,按常理说<code>const void*</code><code>const char*</code>也应该无效,所以这些实例也应该标注<code>delete</code>:</p>
<pre><code class="language-cpp">template&lt;&gt;
void processPointer&lt;const void&gt;(const void*) = delete;
template&lt;&gt;
void processPointer&lt;const char&gt;(const char*) = delete;
</code></pre>
<p>如果你想做得更彻底一些,你还要删除<code>const volatile void*</code><code>const volatile char*</code>重载版本,另外还需要一并删除其他标准字符类型的重载版本:<code>std::wchar_t</code><code>std::char16_t</code><code>std::char32_t</code></p>
<p>有趣的是,如果的类里面有一个函数模板,你可能想用<code>private</code>经典的C++98惯例来禁止这些函数模板实例化但是不能这样做因为不能给特化的成员模板函数指定一个不同于主函数模板的访问级别。如果<code>processPointer</code>是类<code>Widget</code>里面的模板函数, 你想禁止它接受<code>void*</code>参数那么通过下面这样C++98的方法就不能通过编译</p>
<pre><code class="language-cpp">class Widget {
public:
template&lt;typename T&gt;
void processPointer(T* ptr)
{ … }
private:
template&lt;&gt; //错误!
void processPointer&lt;void&gt;(void*);
};
</code></pre>
<p>问题是模板特例化必须位于一个命名空间作用域,而不是类作用域。<em>deleted</em>函数不会出现这个问题,因为它不需要一个不同的访问级别,且他们可以在类外被删除(因此位于命名空间作用域):</p>
<pre><code class="language-cpp">class Widget {
public:
template&lt;typename T&gt;
void processPointer(T* ptr)
{ … }
};
template&lt;&gt; //还是public
void Widget::processPointer&lt;void&gt;(void*) = delete; //但是已经被删除了
</code></pre>
<p>事实上C++98的最佳实践即声明函数为<code>private</code>但不定义是在做C++11 <em>deleted</em>函数要做的事情。作为模仿者C++98的方法不是十全十美。它不能在类外正常工作不能总是在类中正常工作它的罢工可能直到链接时才会表现出来。所以请坚定不移的使用<em>deleted</em>函数。</p>
<p><strong>请记住:</strong></p>
<ul>
<li>比起声明函数为<code>private</code>但不定义,使用<em>deleted</em>函数更好</li>
<li>任何函数都能被删除be deleted包括非成员函数和模板实例译注实例化的函数</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../3.MovingToModernCpp/item10.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="../3.MovingToModernCpp/item12.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../3.MovingToModernCpp/item10.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="../3.MovingToModernCpp/item12.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script type="text/javascript">
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="../book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
</body>
</html>