Item7 Distinguish between () and {} when creating objects

   在引入C++11后变量的初始化方式多种多样,对于每种初始化的方式的区别和联系是一个让我很迷惑地方

  int x(0);
  int y = 0;
  int z{0};
  int c = {0};

   c++通常把c = {0}这种初始化方式看成和z{0}一样,那么x(0)y = 0又有什么区别呢?,对于基本类型来说没有任何区别,对于自定义类型则不一样:

Widget w1(2);   //调用的默认构造函数
Widget w2 = w1; //调用的是拷贝构造函数
w1 = w2;        //调用的赋值操作符

   而{}这种初始化方式调用的是带有initializer_list的构造函数,这就是{}()的一个区别之处。

   C++11中我们可以给类的成员变量之间赋初值,这个特性还是很友好的,但是你不能使用x(0)这样的初始化方式。

int x{0};
int y = 0;
int z(0);   //error

   这就是{}()的另外一个区别之处。在C++11中引入的std::atomic是一个不可拷贝的对象,对于它的初始化是不能利用 y = 0这种形式的,因为它会调用默认拷贝构造函数。

std::atomic<int> ail{0};
std::atomic<int> ai3 = 0;   //error

   y = 0x(0) 这两种形式都有其不适用的地方,而z{0}这种形式则都可以适用,这也就是为什么在c++11中这种初始化方式被称为统一初始化的原因吧。除此之外统一初始化的这种方式还可以避免窄化的转换。

double x,y,z;
int sum1{x + y + z};    //error 窄化转换,报错
int sum2 = x + y + z;   //fine
int sum3(x + y + z);    //fine

   使用统一初始化的方式还有另外一个好处就是避免了C++复杂的语法分析。

Widget w2();    // 对于C++编译器来说需要区别这是一个函数声明还是一个变量的初始化

   如果上面的w2使用了{}统一初始化的方式就避免了复杂的语法分析问题的产生。按照上面的分析我应该鼓励大家使用统一初始化,毕竟上面的这些优点还是很赞的,话又说回来了,C++什么时候有过没啥坑的特性了统一初始化也是一样,有一些不足之处,在Item2中介绍过对于统一初始化auto得到的类型是std::initializer_list类型,此外还容易和普通的初始化方式产生不一致的行为。

class Widget {
 public:
    Widget(int i,bool b);
    Widget(int i,double b);
    Widget(std::initializer_list<long double> il);
};

Widget(10,true);    //调用的是第一个构造函数,
Widget{10,true};    //按理应该是调用第一个构造函数,但是现在却调用了带初始化列表的构造函数

   究其原因就是统一初始化是允许宽化转换的,所以上面10true都转换成long double了。更有甚者编译器会优先匹配std::initializer_list即使不成功也会去匹配。

class Widget {
 public:
    Widget(int i,bool b);
    Widget(int i,double b);
    Widget(std::initializer_list<bool> il);
};

Widget w{10,5.0};   //error 10窄化转换成bool了

   那岂不是只要使用了{}进行统一初始化都会匹配带有std::initializer_list的构造函数吗?,也不完全是这样因为int可以隐式转换成bool所以会优先匹配,如果没法转换了,那么还是会老老实实匹配普通的构造函数的。

class Widget {
 public:
    Widget(int i,bool b);
    Widget(int i,double b);
    Widget(std::initializer_list<string> il);
};

Widget w{10,5.0};   //匹配第一个构造函数,因为10和5.0都无法隐式转换成string

看完了上面的例子后,再来看一个边界情况的例子:

class Widget {
 public:
    Widget();
    Widget(std::initializer_list<int> il);
};

Widget w1;      // 调用默认的构造函数
Widget w2{}     // 也是调用默认的构造函数

   这下有点晕了,上面不是说了,在使用{}这种方式进行初始化的时候选择的不是带有std::initializer_list的构造函数吗?。这里怎么和上面说的不一致呢? 没办法,这是一个特例,如果你想让他调用带有初始化列表的构造函数,你需要像下面这样来调用它:

Widget w3({});
Widget w4{{}};  // ditto

在我们知道了{}()的一些坑后,我们可以去看看标准库中的vector。

std::vector<int> v1(10,20); //使用的是非初始化列表的版本,10个元素,每个元素的值是20
std::vector<int> v2{10,20}; //使用的带初始化列表的版本,2个元素,值分别是10,20

   如果不知道{}()的一些不同的话,很容易认为上面两种形式是一致的,尽管{}()初始化的方式有很多的不同,使得我们在使用的过程中会造成一定的困扰,但是只要我们保持一致这种困扰就会少了许多,避免{}()初始化混杂在一起。
    在一个模版中对于{}()的选择更是无迹可寻,例如下面这个模版

  template<typename T,                // type of object to create
            typename... Ts>            // types of arguments to use
   void doSomeWork(Ts&&... params)
   {
     // create local T object from params...
... }

对于上面的模版的函数体,可以替换成如下两种形式,但是对于传入的不同参数就会产生不可预期的结果

T localObject(std::forward<Ts>(params)...);
T localObject{std::forward<Ts>(params)...}; 

// 如果此时传入下面这代代码:
std::vector<int> v;
...
doSomeWork<std::vector<int>>(10, 20);
如果使用第一种方式就是创建10个元素,每个元素的值是20,如果是第二种形式就是创建两个元素1020

   对于上面这种情况,模版的作者其实也不知道预期的结果应该是什么样的,只有调用者是知道的,对于这类问题实在是没有很好的解决方案,只能通过注释的方式表明,标准库中的std::make_sharedstd::make_unique具有同样的问题,他们使用()初始化、并在代码中进行了注释。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Recently, the renowned actor Zhang Songwen has sparked a fascinating phenomenon known as "two-way rejection", which has captured the attention of many and inspired the masses. The roots of this phenomenon are complex, with one of the fundamental causes being the fear of failure that plagues most of us. Rejection can instill a sense of inadequacy and a fear of being perceived as a failure, which can be challenging to overcome. However, the concept of "two-way rejection" teaches us that rejection is a natural part of life, and it's acceptable to reject and be rejected in return. This empowers us to recognize that life is not just about failures, but also about perseverance, and striving to achieve our aspirations, which may include fame and fortune. Despite the distractions we may encounter, the concept of "two-way rejection" reminds us to turn away from erroneous opportunities and remain steadfast in our principles and moral compass. While there are both advantages and drawbacks to this approach, "two-way rejection" ultimately inspires us to embrace rejection, learn from it, and emerge stronger and more self-assured. However, it is essential to distinguish between a sound and an unsound opportunity to avoid blindly rejecting the right ones. In conclusion, the concept of "two-way rejection" should be approached with discretion, but it can prove to be a valuable tool in enabling us to adhere to our goals and persevere through rejection. It teaches us to embrace rejection, learn from it, and move forward with confidence, ultimately empowering us to achieve our dreams and aspirations.结合双向拒绝进行内容补充
最新发布
05-10

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值