2022-06-30 10:23:03 +08:00
<!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 7:区别使用()和{}创建对象 - Effective Modern C++< / title >
2022-06-30 10:23:03 +08:00
<!-- 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" >
2023-05-06 14:19:14 +08:00
< 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" class = "active" > 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" > 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
2022-06-30 10:23:03 +08:00
< / 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 >
2022-09-08 16:47:52 +08:00
< 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 >
2022-06-30 10:23:03 +08:00
< / 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 >
< h1 id = "第3章-移步现代c" > < a class = "header" href = "#第3章-移步现代c" > 第3章 移步现代C++< / a > < / h1 >
< p > < strong > CHAPTER 3 Moving to Modern C++< / strong > < / p >
2023-03-05 22:37:13 +08:00
< p > 说起知名的特性, C++11/14有一大堆可以吹的东西, < code > auto< / code > ,智能指针(< em > smart pointer< / em > ),移动语义(< em > move semantics< / em > ) , < em > lambda< / em > ,并发(< em > concurrency< / em > ) ——每个都是如此的重要, 这章将覆盖这些内容。掌握这些特性是必要的, 要想成为高效率的现代C++程序员需要小步迈进。在从C++98小步迈进到现代C++过程中遇到的每个问题,本章都会一一回答。你什么时候应该用{}而不是()创建对象?为什么别名(< em > alias< / em > )声明比< code > typedef< / code > 好?< code > constexpr< / code > 和< code > const< / code > 有什么不同?常量(< code > const< / code > )成员函数和线程安全有什么关系?这个列表越列越多,这章将会逐个回答这些问题。< / p >
2022-06-30 10:23:03 +08:00
< h2 id = "条款七区别使用和创建对象" > < a class = "header" href = "#条款七区别使用和创建对象" > 条款七:区别使用< code > ()< / code > 和< code > {}< / code > 创建对象< / a > < / h2 >
< p > < strong > Item 7: Distinguish between < code > ()< / code > and < code > {}< / code > when creating objects< / strong > < / p >
2023-03-05 22:37:13 +08:00
< p > 取决于你看问题的角度, C++11对象初始化的语法可能会让你觉得丰富的让人难以选择, 亦或是乱的一塌糊涂。一般来说, 初始化值要用圆括号()或者花括号{}括起来,或者放到等号" =" 的右边:< / p >
< pre > < code class = "language-cpp" > int x(0); //使用圆括号初始化
2022-06-30 10:23:03 +08:00
int y = 0; //使用" =" 初始化
int z{ 0 }; //使用花括号初始化
< / code > < / pre >
< p > 在很多情况下,你可以使用" =" 和花括号的组合:< / p >
< pre > < code class = "language-cpp" > int z = { 0 }; //使用" =" 和花括号
< / code > < / pre >
< p > 在这个条款的剩下部分,我通常会忽略" =" 和花括号组合初始化的语法, 因为C++通常把它视作和只有花括号一样。< / p >
2023-03-05 22:37:13 +08:00
< p > “乱的一塌糊涂”是指在初始化中使用" =" 可能会误导C++新手,使他们以为这里发生了赋值运算,然而实际并没有。对于像< code > int< / code > 这样的内置类型,研究两者区别就像在做学术,但是对于用户定义的类型而言,区别赋值运算符和初始化就非常重要了,因为它们涉及不同的函数调用:< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > Widget w1; //调用默认构造函数
Widget w2 = w1; //不是赋值运算,调用拷贝构造函数
w1 = w2; //是赋值运算, 调用拷贝赋值运算符( copy operator=)
< / code > < / pre >
2023-03-05 22:37:13 +08:00
< p > 甚至对于一些初始化语法, 在一些情况下C++98没有办法表达预期的初始化行为。举个例子, 要想直接创建并初始化一个存放一些特殊值的STL容器是不可能的( 比如1,3,5) 。< / p >
< p > C++11使用统一初始化( < em > uniform initialization< / em > )来整合这些混乱且不适于所有情景的初始化语法,所谓统一初始化是指在任何涉及初始化的地方都使用单一的初始化语法。
它基于花括号,出于这个原因我更喜欢称之为括号初始化。(< strong > 译注:注意,这里的括号初始化指的是花括号初始化,在没有歧义的情况下下文的括号初始化指的都是用花括号进行初始化;当与圆括号初始化同时存在并可能产生歧义时我会直接指出。< / strong > )统一初始化是一个概念上的东西,而括号初始化是一个具体语法结构。< / p >
2023-03-06 21:30:22 +08:00
< p > 括号初始化让你可以表达以前表达不出的东西。使用花括号,创建并指定一个容器的初始元素变得很容易:< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > std::vector< int> v{ 1, 3, 5 }; //v初始内容为1,3,5
< / code > < / pre >
< p > 括号初始化也能被用于为非静态数据成员指定默认初始值。C++11允许" =" 初始化不加花括号也拥有这种能力:< / p >
< pre > < code class = "language-cpp" > class Widget{
…
private:
int x{ 0 }; //没问题, x初始值为0
int y = 0; //也可以
int z(0); //错误!
}
< / code > < / pre >
2023-03-05 22:37:13 +08:00
< p > 另一方面,不可拷贝的对象(例如< code > std::atomic< / code > ——见< a href = "../7.TheConcurrencyAPI/item40.html" > Item40< / a > )可以使用花括号初始化或者圆括号初始化,但是不能使用" =" 初始化:< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > std::atomic< int> ai1{ 0 }; //没问题
std::atomic< int> ai2(0); //没问题
std::atomic< int> ai3 = 0; //错误!
< / code > < / pre >
2023-03-06 21:30:22 +08:00
< p > 因此我们很容易理解为什么括号初始化又叫统一初始化, 在C++中这三种方式都被看做是初始化表达式,但是只有花括号任何地方都能被使用。< / p >
< p > 括号表达式还有一个少见的特性,即它不允许内置类型间隐式的变窄转换(< em > narrowing conversion< / em > )。如果一个使用了括号初始化的表达式的值,不能保证由被初始化的对象的类型来表示,代码就不会通过编译:< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > double x, y, z;
int sum1{ x + y + z }; //错误! double的和可能不能表示为int
< / code > < / pre >
2023-03-06 21:30:22 +08:00
< p > 使用圆括号和" =" 的初始化不检查是否转换为变窄转换,因为由于历史遗留问题它们必须要兼容老旧代码:< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > int sum2(x + y +z); //可以( 表达式的值被截为int)
int sum3 = x + y + z; //同上
< / code > < / pre >
2023-03-05 22:37:13 +08:00
< p > 另一个值得注意的特性是括号表达式对于C++最令人头疼的解析问题有天生的免疫性。(译注:所谓最令人头疼的解析即< em > most vexing parse< / em > ,更多信息请参见< a href = "https://en.wikipedia.org/wiki/Most_vexing_parse" > https://en.wikipedia.org/wiki/Most_vexing_parse< / a > 。) C++规定任何< em > 可以被解析< / em > 为一个声明的东西< em > 必须被解析< / em > 为声明。这个规则的副作用是让很多程序员备受折磨:他们可能想创建一个使用默认构造函数构造的对象,却不小心变成了函数声明。问题的根源是如果你调用带参构造函数,你可以这样做:< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > Widget w1(10); //使用实参10调用Widget的一个构造函数
< / code > < / pre >
2023-03-05 22:37:13 +08:00
< p > 但是如果你尝试使用相似的语法调用< code > Widget< / code > 无参构造函数,它就会变成函数声明:< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > Widget w2(); //最令人头疼的解析! 声明一个函数w2, 返回Widget
< / code > < / pre >
2023-03-05 22:37:13 +08:00
< p > 由于函数声明中形参列表不能带花括号,所以使用花括号初始化表明你想调用默认构造函数构造对象就没有问题:< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > Widget w3{}; //调用没有参数的构造函数构造对象
< / code > < / pre >
2023-03-05 22:37:13 +08:00
< p > 关于括号初始化还有很多要说的。它的语法能用于各种不同的上下文, 它防止了隐式的变窄转换, 而且对于C++最令人头疼的解析也天生免疫。既然好到这个程度那为什么这个条款不叫“优先考虑括号初始化语法”呢?< / p >
2023-03-06 21:30:22 +08:00
< p > 括号初始化的缺点是有时它有一些令人惊讶的行为。这些行为使得括号初始化、< code > std::initializer_list< / code > 和构造函数参与重载决议时本来就不清不楚的暧昧关系进一步混乱。把它们放到一起会让看起来应该左转的代码右转。举个例子,< a href = "../1.DeducingTypes/item2.html" > Item2< / a > 解释了当< code > auto< / code > 声明的变量使用花括号初始化,变量类型会被推导为< code > std::initializer_list< / code > ,但是使用相同内容的其他初始化方式会产生更符合直觉的结果。所以,你越喜欢用< code > auto< / code > ,你就越不能用括号初始化。< / p >
2023-03-05 22:37:13 +08:00
< p > 在构造函数调用中,只要不包含< code > std::initializer_list< / code > 形参,那么花括号初始化和圆括号初始化都会产生一样的结果:< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > class Widget {
public:
Widget(int i, bool b); //构造函数未声明
2023-03-06 21:30:22 +08:00
Widget(int i, double d); //std::initializer_list这个形参
2022-06-30 10:23:03 +08:00
…
};
Widget w1(10, true); //调用第一个构造函数
Widget w2{10, true}; //也调用第一个构造函数
Widget w3(10, 5.0); //调用第二个构造函数
Widget w4{10, 5.0}; //也调用第二个构造函数
< / code > < / pre >
2023-03-06 21:30:22 +08:00
< p > 然而,如果有一个或者多个构造函数的声明包含一个< code > std::initializer_list< / code > 形参,那么使用括号初始化语法的调用更倾向于选择带< code > std::initializer_list< / code > 的那个构造函数。如果编译器遇到一个括号初始化并且有一个带std::initializer_list的构造函数, 那么它一定会选择该构造函数。如果上面的< code > Widget< / code > 类有一个< code > std::initializer_list< long double> < / code > 作为参数的构造函数,就像这样:< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > class Widget {
public:
Widget(int i, bool b); //同上
Widget(int i, double d); //同上
Widget(std::initializer_list< long double> il); //新添加的
…
};
< / code > < / pre >
2023-03-06 21:30:22 +08:00
< p > < code > w2< / code > 和< code > w4< / code > 将会使用新添加的构造函数,即使另一个非< code > std::initializer_list< / code > 构造函数和实参更匹配:< / p >
2023-03-05 22:37:13 +08:00
< pre > < code class = "language-cpp" > Widget w1(10, true); //使用圆括号初始化,同之前一样
2022-06-30 10:23:03 +08:00
//调用第一个构造函数
Widget w2{10, true}; //使用花括号初始化,但是现在
2023-03-06 21:30:22 +08:00
//调用带std::initializer_list的构造函数
2022-06-30 10:23:03 +08:00
//(10 和 true 转化为long double)
2023-03-05 22:37:13 +08:00
Widget w3(10, 5.0); //使用圆括号初始化,同之前一样
2022-06-30 10:23:03 +08:00
//调用第二个构造函数
Widget w4{10, 5.0}; //使用花括号初始化,但是现在
2023-03-06 21:30:22 +08:00
//调用带std::initializer_list的构造函数
2022-06-30 10:23:03 +08:00
//(10 和 5.0 转化为long double)
< / code > < / pre >
2023-03-06 21:30:22 +08:00
< p > 甚至普通构造函数和移动构造函数都会被带< code > std::initializer_list< / code > 的构造函数劫持:< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > class Widget {
public:
Widget(int i, bool b); //同之前一样
Widget(int i, double d); //同之前一样
Widget(std::initializer_list< long double> il); //同之前一样
operator float() const; //转换为float
…
};
2023-03-06 21:30:22 +08:00
Widget w5(w4); //使用圆括号,调用拷贝构造函数
2022-06-30 10:23:03 +08:00
Widget w6{w4}; //使用花括号, 调用std::initializer_list构造
//函数( w4转换为float, float转换为double)
2023-03-06 21:30:22 +08:00
Widget w7(std::move(w4)); //使用圆括号,调用移动构造函数
2022-06-30 10:23:03 +08:00
Widget w8{std::move(w4)}; //使用花括号, 调用std::initializer_list构造
//函数( 与w6相同原因)
< / code > < / pre >
2023-03-06 21:30:22 +08:00
< p > 编译器一遇到括号初始化就选择带< code > std::initializer_list< / code > 的构造函数的决心是如此强烈,以至于就算带< code > std::initializer_list< / code > 的构造函数不能被调用,它也会硬选。< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > class Widget {
public:
Widget(int i, bool b); //同之前一样
Widget(int i, double d); //同之前一样
Widget(std::initializer_list< bool> il); //现在元素类型为bool
… //没有隐式转换函数
};
Widget w{10, 5.0}; //错误!要求变窄转换
< / code > < / pre >
2023-03-06 21:30:22 +08:00
< p > 这里,编译器会直接忽略前面两个构造函数(其中第二个构造函数是所有实参类型的最佳匹配),然后尝试调用< code > std::initializer_list< bool> < / code > 构造函数。调用这个函数将会把< code > int(10)< / code > 和< code > double(5.0)< / code > 转换为< code > bool< / code > ,由于会产生变窄转换(< code > bool< / code > 不能准确表示其中任何一个值),括号初始化拒绝变窄转换,所以这个调用无效,代码无法通过编译。< / p >
2022-06-30 10:23:03 +08:00
< p > 只有当没办法把括号初始化中实参的类型转化为< code > std::initializer_list< / code > 时,编译器才会回到正常的函数决议流程中。比如我们在构造函数中用< code > std::initializer_list< std::string> < / code > 代替< code > std::initializer_list< bool> < / code > ,这时非< code > std::initializer_list< / code > 构造函数将再次成为函数决议的候选者,因为没有办法把< code > int< / code > 和< code > bool< / code > 转换为< code > std::string< / code > :< / p >
< pre > < code class = "language-cpp" > class Widget {
public:
Widget(int i, bool b); //同之前一样
Widget(int i, double d); //同之前一样
//现在std::initializer_list元素类型为std::string
Widget(std::initializer_list< std::string> il);
… //没有隐式转换函数
};
2023-03-05 22:37:13 +08:00
Widget w1(10, true); // 使用圆括号初始化,调用第一个构造函数
2022-06-30 10:23:03 +08:00
Widget w2{10, true}; // 使用花括号初始化,现在调用第一个构造函数
2023-03-05 22:37:13 +08:00
Widget w3(10, 5.0); // 使用圆括号初始化,调用第二个构造函数
2022-06-30 10:23:03 +08:00
Widget w4{10, 5.0}; // 使用花括号初始化,现在调用第二个构造函数
< / code > < / pre >
2023-03-06 21:30:22 +08:00
< p > 代码的行为和我们刚刚的论述如出一辙。这里还有一个有趣的< a href = "https://en.wikipedia.org/wiki/Edge_case" > 边缘情况< / a > 。假如你使用的花括号初始化是空集,并且你欲构建的对象有默认构造函数,也有< code > std::initializer_list< / code > 构造函数。你的空的花括号意味着什么?如果它们意味着没有实参,就该使用默认构造函数,但如果它意味着一个空的< code > std::initializer_list< / code > ,就该调用< code > std::initializer_list< / code > 构造函数。< / p >
2022-06-30 10:23:03 +08:00
< p > 最终会调用默认构造函数。空的花括号意味着没有实参,不是一个空的< code > std::initializer_list< / code > : < / p >
< pre > < code class = "language-cpp" > class Widget {
public:
Widget(); //默认构造函数
Widget(std::initializer_list< int> il); //std::initializer_list构造函数
… //没有隐式转换函数
};
Widget w1; //调用默认构造函数
Widget w2{}; //也调用默认构造函数
Widget w3(); //最令人头疼的解析!声明一个函数
< / code > < / pre >
2023-03-06 21:30:22 +08:00
< p > 如果你< strong > 想< / strong > 用空< code > std::initializer< / code > 来调用< code > std::initializer_list< / code > 构造函数,你就得创建一个空花括号作为函数实参——把空花括号放在圆括号或者另一个花括号内来界定你想传递的东西。< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > Widget w4({}); //使用空花括号列表调用std::initializer_list构造函数
Widget w5{{}}; //同上
< / code > < / pre >
2023-03-06 21:30:22 +08:00
< p > 此时,括号初始化,< code > std::initializer_list< / code > 和构造函数重载的晦涩规则就会一下子涌进你的脑袋,你可能会想研究半天这些东西在你的日常编程中到底占多大比例。可能比你想象的要有用。因为< code > std::vector< / code > 作为受众之一会直接受到影响。< code > std::vector< / code > 有一个非< code > std::initializer_list< / code > 构造函数允许你去指定容器的初始大小,以及使用一个值填满你的容器。但它也有一个< code > std::initializer_list< / code > 构造函数允许你使用花括号里面的值初始化容器。如果你创建一个数值类型的< code > std::vector< / code > (比如< code > std::vector< int> < / code > ),然后你传递两个实参,把这两个实参放到圆括号和放到花括号中有天壤之别:< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > std::vector< int> v1(10, 20); //使用非std::initializer_list构造函数
//创建一个包含10个元素的std::vector,
//所有的元素的值都是20
std::vector< int> v2{10, 20}; //使用std::initializer_list构造函数
//创建包含两个元素的std::vector,
//元素的值为10和20
< / code > < / pre >
2023-03-06 21:30:22 +08:00
< p > 让我们回到之前的话题。从以上讨论中我们得出两个重要结论。第一,作为一个类库作者,你需要意识到如果一堆重载的构造函数中有一个或者多个含有< code > std::initializer_list< / code > 形参,用户代码如果使用了括号初始化,可能只会看到你< code > std::initializer_list< / code > 版本的重载的构造函数。因此,你最好把你的构造函数设计为不管用户是使用圆括号还是使用花括号进行初始化都不会有什么影响。换句话说,了解了< code > std::vector< / code > 设计缺点后,你以后设计类的时候应该避免诸如此类的问题。< / p >
2022-06-30 10:23:03 +08:00
< p > 这里的暗语是如果一个类没有< code > std::initializer_list< / code > 构造函数,然后你添加一个,用户代码中如果使用括号初始化,可能会发现过去被决议为非< code > std::initializer_list< / code > 构造函数而现在被决议为新的函数。当然,这种事情也可能发生在你添加一个函数到那堆重载函数的时候:过去被决议为旧的重载函数而现在调用了新的函数。< code > std::initializer_list< / code > 重载不会和其他重载函数比较,它直接盖过了其它重载函数,其它重载函数几乎不会被考虑。所以如果你要加入< code > std::initializer_list< / code > 构造函数,请三思而后行。< / p >
2023-03-06 21:30:22 +08:00
< p > 第二, 作为一个类库使用者, 你必须认真的在花括号和圆括号之间选择一个来创建对象。大多数开发者都使用其中一种作为默认情况, 只有当他们不能使用这种的时候才会考虑另一种。默认使用花括号初始化的开发者主要被适用面广、禁止变窄转换、免疫C++最令人头疼的解析这些优点所吸引。这些开发者知道在一些情况下(比如给定一个容器大小和一个初始值创建< code > std::vector< / code > ) 要使用圆括号。默认使用圆括号初始化的开发者主要被C++98语法一致性、避免< code > std::initializer_list< / code > 自动类型推导、避免不会不经意间调用< code > std::initializer_list< / code > 构造函数这些优点所吸引。这些开发者也承认有时候只能使用花括号(比如创建一个包含着特定值的容器)。关于花括号初始化和圆括号初始化哪种更好大家没有达成一致,所以我的建议是选择一种并坚持使用它。< / p >
< p > 如果你是一个模板的作者,花括号和圆括号创建对象就更麻烦了。通常不能知晓哪个会被使用。举个例子,假如你想创建一个接受任意数量的参数来创建的对象。使用可变参数模板(< em > variadic template< / em > )可以非常简单的解决:< / p >
2022-06-30 10:23:03 +08:00
< pre > < code class = "language-cpp" > template< typename T, //要创建的对象类型
typename... Ts> //要使用的实参的类型
void doSomeWork(Ts& & ... params)
{
create local T object from params...
…
}
< / code > < / pre >
2022-11-25 13:43:31 +08:00
< p > 在现实中我们有两种方式实现这个伪代码(关于< code > std::forward< / code > 请参见< a href = "../5.RRefMovSemPerfForw/item25.html" > Item25< / a > ) : < / p >
2023-03-06 21:30:22 +08:00
< pre > < code class = "language-cpp" > T localObject(std::forward< Ts> (params)...); //使用圆括号
2022-06-30 10:23:03 +08:00
T localObject{std::forward< Ts> (params)...}; //使用花括号
< / code > < / pre >
< p > 考虑这样的调用代码:< / p >
< pre > < code class = "language-cpp" > std::vector< int> v;
…
doSomeWork< std::vector< int> > (10, 20);
< / code > < / pre >
2023-03-06 21:30:22 +08:00
< p > 如果< code > doSomeWork< / code > 创建< code > localObject< / code > 时使用的是圆括号,< code > std::vector< / code > 就会包含10个元素。如果< code > doSomeWork< / code > 创建< code > localObject< / code > 时使用的是花括号,< code > std::vector< / code > 就会包含2个元素。哪个是正确的? < code > doSomeWork< / code > 的作者不知道,只有调用者知道。< / p >
< p > 这正是标准库函数< code > std::make_unique< / code > 和< code > std::make_shared< / code > (参见< a href = "../4.SmartPointers/item21.html" > Item21< / a > )面对的问题。它们的解决方案是使用圆括号,并被记录在文档中作为接口的一部分。(注:更灵活的设计——允许调用者决定从模板来的函数应该使用圆括号还是花括号——是有可能的。详情参见< a href = "http://akrzemi1.wordpress.com/" > Andrzej’ s C++ blog< / a > 在2013年6月5日的文章, “< a href = "http://akrzemi1.wordpress.com/2013/06/05/intuitive-interface-part-i/" > Intuitive interface — Part I.< / a > ”)< / p >
2022-06-30 10:23:03 +08:00
< p > < strong > 请记住:< / strong > < / p >
< ul >
2023-04-22 11:34:45 +08:00
< li > 花括号初始化是最广泛使用的初始化语法, 它防止变窄转换, 并且对于C++最令人头疼的解析有天生的免疫性< / li >
2023-03-06 21:30:22 +08:00
< li > 在构造函数重载决议中,编译器会尽最大努力将括号初始化与< code > std::initializer_list< / code > 参数匹配,即便其他构造函数看起来是更好的选择< / li >
2023-03-05 22:37:13 +08:00
< li > 对于数值类型的< code > std::vector< / code > 来说使用花括号初始化和圆括号初始化会造成巨大的不同< / li >
< li > 在模板类选择使用圆括号初始化或使用花括号初始化创建对象是一个挑战。< / li >
2022-06-30 10:23:03 +08:00
< / ul >
< / main >
< nav class = "nav-wrapper" aria-label = "Page navigation" >
<!-- Mobile navigation buttons -->
< a rel = "prev" href = "../2.Auto/item6.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/item8.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 = "../2.Auto/item6.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/item8.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 >