Effective Modern C++ Item 7 总结:关于两种对象创建方法“()、{}”的区分
来源:互联网 发布:amdcpu超频软件 编辑:程序博客网 时间:2024/05/21 02:20
今天学习了《Effective Modern C++》Item 7 Distinguish () and {} when creating objects. 为了防止以后遗忘,在这里总结一下。
C++11 标准中,有三种对象的初始化方式,分别为 ()、{}、= 。代码如下所示:
int x(0); // way 1. initializer is in parentheses int y = 0; // way 2. initializer follows "=" int z{0}; // way 3. initializer is in braces有些情况下 ,也可用等号(=)和大括号({})结合来初始化对象。例如:
int z = {0}; // way 4. initializer uses "=" and braces在这里,我们将way3 和way4合并作为同一种初始化方式,即用大括号的方式进行初始化。
在以上3中对象创建方式中,使用大括号的方式是适用性最高的方式,其他两种方式则在某些使用情境下会产生编译错误,比如:
在类的定义中,就不能用小括号"()"的方法初始化成员变量:
class Widget{public:Widget() {};private:int a{0}; // fine. a's default value is 0.int b = 10; // fine. b's default value is 10. int c(20); // error!};
另一方面,对于那些“uncopyable”的对象,则不能使用“=”初始化。
std::atomic<int> ai1{0}; // finestd::atomic<int> ai2(0); // finestd::atomic<int> ai3 = 0; // error
所以,在三种初始化对象的方式中,只有“大括号”是最为通用的。那么,我们在使用大括号的方式进行对象初始化时要注意些什么呢?或者大括号初始化方式有些什么特点呢?
特点1:阻止内置类型间的缩小转换(narrowing conversation)。
缩小转换是指将一个对象从表示范围大的类型(如double)转换为表示范围小的类型(如int),大括号初始化可以阻止内置类型间的这种转换。
double x, y, z;int sum1{x + y + z}; // error! sum of doubles may not be expressible as int
特点2:避免类对象定义中二义性的产生。
假设有一个名字叫做“Widget”的类,有如下类对象的定义:
Widget w1(10); // 很好,调用Widget的有一个整形参数的构造函数Widget w2(); //产生二义性,是调用Widget的默认无参构造函数?还是生命了一个名字为w2,返回类型为Widget的无参函数?
如果改为用大括号来初始化对象,就不会产生二义性了:
Widget w1{10}; // 很好,像以前一样,调用Widget中有一个参数的那个构造函数Widget w2{}; // 很好,调用Widget的无参构造函数
以上两个特点,算是大括号初始化方式给我们带来的惊喜,但伴随着这些惊喜,大括号初始化方式也带来一些令人不那么愉快的副作用。
副作用1:对auto类型推导的影响。
当auto遇上大括号时,类型推导机制会将变量推导为std::initializer_list<T>类型:
auto v1 = -1; // -1 是int类型, 所以v1是int类型auto v2(-1); // -1 是int类型, 所以v2是int类型auto v3{-1}; // -1 仍然是int, 但 v3的类型是 std::initializer_list<int>auto v4 = {-1}; // -1仍然是int, 但v4's的类型是 std::initializer_list<int>
副作用2:对类构造函数的影响。
如果用大括号的方式进行类对象的初始化,编译器会选择含有std::initializer_list<>类型参数的构造函数,即使那些不含std::initializer_list的构造函数更加匹配。
class Widget {public: Widget(int i, bool b); // as before Widget(int i, double d); // as before Widget(std::initializer_list<long double> il); // added…};
Widget对象定义如下:
Widget w1(10, true); //调用第一个构造函数Widget w2{10, true}; //大括号初始化, 调用第三个构造函数Widget w3(10, 5.0); //小括号初始化,调用第二个构造函数Widget w4{10, 5.0}; //使用大括号初始化,调用第三个构造函数在上面的例子中,w2和w4使用大括号方式初始化,尽管第一个、第二个构造函数的参数最能匹配w2和w4的定义,但是编译器仍然会选择第三个构造函数来初始化w2和w4.
如果将Widget的第三个构造函数改为:
Widget(std::initializer_list<bool> il);
编译器仍然为会为w4调用第三个构造函数,但是注意,这时会发生缩小转换,这是大括号初始化方式所禁止的,所以下面的声明会报错:
Widget w{10, 5.0}; // error! requires narrowing conversions
那么问题来了,如果用户既想用大括号初始化方式定义对象,又不想让编译器匹配第三个构造函数,应该怎么办呢?以本文中的Widget为例,可以将第三个构造函数改为如下形式:
// std::init_list element type is now std::stringWidget(std::initializer_list<std::string> il);
此时,变量定义及调用的构造函数如下:
Widget w1(10, true); // 使用小括号, 仍然调用第一个构造函数Widget w2{10, true}; // 使用大括号,但是不存在从(int bool)到string的转换,所以仍然匹配第一个构造函数Widget w3(10, 5.0); // 使用小括号, 匹配第二个构造函数Widget w4{10, 5.0}; // 使用大括号,但是不存在从(int double)到string的转换,所以仍然匹配第二个构造函数
通过上面的例子,编译器为大括号初始化方式匹配第三个构造函数(形参为std::initializer_list<T>)的前提是存在实参到T的类型转换。
两个例外:
在用大括号进行初始化的时候有两个例外:
例外1. 空大括号意味着无参,而不是空的std::initializer_list. 在对象的定义中会调用类的无参构造函数。
例如有如下类定义:
class Widget { public: Widget(); //默认构造函数 Widget(std::initializer_list<int> il); … //};
有如下对象定义
Widget w1; // 调用默认构造函数Widget w2{}; // 仍然调用默认构造函数,而不是创建空的 std::initializer_listWidget w3(); // 二义性,定义了一个函数
如果想让编译器匹配第二个构造函数,则对象定义格式应如下所示:
Widget w4({}); // 调用第二个构造函数
w5{{}}; // 调用第二个构造函数例外2 大括号初始化方式不影响拷贝和移动构造函数 :
设有类定义如下:
class Widget {public: Widget(const Widget& rhs); // 1 copy constructor Widget(Widget&& rhs); // move constructor Widget(std::initializer_list<int> il); // std::init_list constructor operator int() const; // convert to int …};
auto w6{w5} //调用拷贝构造函数,而不是std::initializer_list 构造函数
auto w7{std::move(w5)}; //调用move constructor
- Effective Modern C++ Item 7 总结:关于两种对象创建方法“()、{}”的区分
- Effective Modern C++: Item 7 -> 创建对象时分清()和{}
- 《Effective Modern C++》Item 1总结
- 《Effective Modern C++》Item 2总结
- Effective Modern C++》Item 3总结
- 《Effective C#》Item 2:定义常量的两种方法
- 《Effective C#》Item 2:定义常量的两种方法
- Effective Modern C++: Item 3 ->弄清decltype
- 转:《Effective C#》Item 2:定义常量的两种方法
- Effective Modern C++ 条款7 创建对象时区分( )和{ }
- Effective Modern C++: Item 9 -> 优先选择别名声明(alias declaration)而不是typedef
- Effective Modern C++:Item 2 ->弄清auto类型推断
- Effective Modern C++: Item 4 -> 知道如何查看推断类型
- Effective Modern C++: Item 12 -> 声明覆盖函数override
- Effective Modern C++: Item 13 -> 优先选择const_iterators而不是iterators
- Effective Modern C++ Item Lists
- Effective Modern C++ Item 1
- Effective Modern C++ Item 2
- php递归调用实现
- APP混淆全攻略
- Eclipse 安装 SVN 插件的两种方法
- JVM 诊断调优 CheatSheet
- 常用正则表达式大全
- Effective Modern C++ Item 7 总结:关于两种对象创建方法“()、{}”的区分
- :nth-child和:nth-of-type的区别
- Android仿今日头条视频列表播放
- kubenetes源码分析之DNS(六)
- 理解JAVA 中的代码单元与代码点
- C#保存文件、读取文件对话框
- 单调递增子序列(二)
- centon7上RPM安装Mysql5.5
- 十大经典排序算法