频道栏目
IT货架 > > 正文
[麦片笔记]关于C++默认构造、复制构造、复制 理论 实践
网友分享于:Aug 2, 2018 1:57:21 PM    来源: IT货架   
[麦片笔记]关于C++默认构造、复制构造、复制复制构造函数,赋值运算符,析构函数号称C++三大复制控制函数的说,之前看《C++ primer》看过一点,但是今天看某大牛的模板各种看不懂啊,于是就又复习了下,结果发现个比较有意思的地方.理论《C++Primer》说到:

复制构造函数是只有单个形参,而且该形参是对本类型对象的引用的一个构造函数。在以下几种情况下,会调用复制构造函数:

  1. 根据另一个同类型的对象显式或隐式初始化一个对象。

  2. 复制一个对象,将它作为实参传给一个函数。

  3. 从函数返回时复制一个对象。

  4. 初始化顺序容器中的元素。

  5. 根据元素初始化列表初始化数组元素。

情况1是指用一个对象初始化另一个对象时,例如:A a; A b(a); A c = a;上面的b和c对象在初始化时都是根据对象a生成副本的,因此都是调用的复制构造函数。虽然初始化c的方式感觉有点别扭,容易让人认为调用的是operator=,但是operator=调用的时候应该是c对象已经构造出来了的,而上面的代码中c对象还没构造出来。如果写成下面这样的形式,则调用的是operator =A a; A c; c = a;情况2是指某个函数中有一个参数,以某类对象值作为形参,那么当实参传如函数时,便会调用复制构造函数,生成对象的副本,在函数体中使用,如:void func(A a, int i); //其中的A a就会调用复制构造函数情况4是指复制构造函数可用于初始化顺序容器中的元素,如:vector<A> v(5); //会先调用默认构造函数生成一个临时对象,然后使用复制构造函数将临时对象的值构造每个vector的元素。实践嗯,上面说了这么多,那些都是废话,哈哈,下面的才是这篇日志要说的内容,囧。为了验证调用复制构造函数的时机,我定义了这样一个简单的类:class A { public: A() { cout << "A::A()" << endl; } A(string s) { cout << "A::A(string)" << endl; } A(const A& b) { cout << "A::copy()" << endl; } A& operator=(const A& b) { cout << "A::operator()" << endl; return *this; } };情况3指的是一个函数返回类型是一个类对象的时候,那么在函数返回之前,会调用复制构造函数复制返回结果到一个临时对象中去,如:A func(A b) { A c; return c; } A a; A x = func(a);分析一下上面这段代码,理论上来说应该有下面几步:
  • A a; 调用A的默认构造函数 -> A::A()
  • func(A b) 调用复制构造函数,生成实参的副本 -> A::copy()
  • A c; 调用A的默认构造函数 -> A::A()
  • return c; 调用复制构造函数,生成返回值得副本 -> A::copy()
  • A x = func(a); 调用复制构造函数,用函数返回值初始化x -> A::copy()
  • 但是真正把程序编译运行,发现结果却是这样的:A::A() A::copy() A::A()只有传入实参时,才调用了复制构造函数,而第4,5步并没有调用!一开始我以为还是调用了复制构造函数了的,只是编译给生成了一个默认的,而没有调用我们自己定义的函数。因为复制构造函数的函数签名是固定的,并不可能有多个重载版本。
    google了好一会找到这个问答 好吧,正是我们碰到的问题,而导致这个问题的关键是所谓的Named Return Value Optimization (NRVO),大概意思是这样的:

    在上述的场景中设计三个对象:函数内部对象 c,函数自己定义的返回值对象 _R,main函数中的对象x,按理来说是要进行两次复制构造才能完成对x的赋值的,但是根据那个NRVO技术,在某些特定的场景下,可以把c,_R,x这三个对象的合并,从而避免了这两次不必要的复制构造函数调用。(在这里讲解的很清楚)

    NRVO目标是为了减少不必要的对象复制,提高程序的性能,这样复制构造函数的调用时机就不是程序员能把握的了,因此

    还是不要在复制构造函数里放置除了简单复制以外的代码了哈 :)

    要是想让编译器不“自作聪明”,可以用参数 –no-elide-constructors,那么上面程序的输出就是我们期望的了。A::A() A::copy() A::A() A::copy() A::copy()那么对于函数返回值这个情况,算是弄清楚了哈,但是在情况5(根据元素初始化列表初始化数组元素),又碰到类似的问题,oh shit!
    情况5是指用元素初始化列表初始化数组元素,会调用相应的构造函数,然后调用复制构造函数复制到相应的数组元素A arr[5]; //这样是不会调用复制构造函数滴,只会调用5次默认构造函数 A arr[5] = { string("n1"), string("n1"), string("n1"), string("n1"), A() }; //调用4次形参为一个string的构造函数,1次默认构造函数,每次构造完后,调用复制构造函数,复制到数组对应元素但是,输出结果再一次出卖了我 – -!A::A(string) A::A(string) A::A(string) A::A(string) A::A()好吧,我能用之前那一套理论来解释么?应该是编译器用了某些技术把复制构造函数优化掉了哈,具体的我也不想深究了 :(
    用g++ –no-elide-constructors test.cpp也确实能得到我们想要的结果:A::A(string) A::copy() A::A(string) A::copy() A::A(string) A::copy() A::A(string) A::copy() A::A() A::copy()感谢YXB的投稿,YXB推荐的Markdown也确实很好用!
    相关板块推荐 Java PHP Python

    广告服务联系QQ:1134687142 | 网站地图

    版权所有: IT货架- 内容来自互联网,仅供用于技术学习,请遵循相关法律法规. 京ICP备11030978号-1