深入探索C++对象模型-构造函数 阅读笔记

深入探索C++对象模型-构造函数 阅读笔记,第1张

深入探索C++对象模型-构造函数 阅读笔记

文章目录
  • 深入探索C++对象模型-构造函数 阅读笔记
  • 默认构造函数
    • 什么情况下编译器需要合成默认构造函数?
      • 注意
  • 拷贝构造函数
    • 什么情况下编译器需要合成拷贝构造函数?
      • 注意
    • 具名返回值(Named Return Value,NRV)优化问题
      • 注意
  • 初始化列表
    • 什么情况下必须使用初始化列表?
    • 注意
  • 资料


默认构造函数 什么情况下编译器需要合成默认构造函数?

一句话:编译器只会去合成non trivial的默认构造函数。


且用户定义的默认构造函数(即使里面什么都没做)为non-trivial!
下面介绍4中非平凡(non-trivial)情况:

  1. 类成员有non-trival的构造函数(包括用户定义的,或编译合成的,因为需要调用成员构造函数)
  2. 基类有non-trival的构造函数 (因为需要调用基类构造函数)
  3. 有虚函数(因为需要初始化vptr)
  4. 有虚基类 (需要初始化虚基类offset)

上述4中情况下,如果用户定义了构造函数,编译器不会再合成,而会去扩展已定义的构造函数。


(注意如果用户没定义默认构造函数,即无参构造函数,编译器也是不会再合成了)

其余情况,编译器不会合成构造函数,也不会去调用构造函数。


注意
  1. 扩展的构造函数代码会在所有用户代码之前。


  2. 合成的构造函数都是inline方式完成(其他合成的函数如拷贝构造函数一样,避免合成多个造成重定义)
  3. 合成的构造函数只做了“必要的部分”,一些整数、指针等类型的变量是不会初始化的!!
拷贝构造函数 什么情况下编译器需要合成拷贝构造函数?

原理同默认构造函数,编译器只会去合成non trivial的拷贝构造函数。


对于拷贝构造函数而言,trivial即为bitwise拷贝(逐位拷贝)。



也是分为4种情况,同上不再赘述。


注意
  1. 合成的拷贝构造函数,会保证语义正确,即除了调用基类或成员的拷贝构造函数外,一些整数、指针等类型也会被拷贝!!
具名返回值(Named Return Value,NRV)优化问题

编译器将返回值转换为函数的第一个参数,如下:

X bar(int) {
	X xx;
	// 处理 xx ...
	return xx; // named return value
}
// 调用:
X xx = bar(0);

编译器转换为:

void bar(X &__result, int) {
	// __result 只需要分配内存,不需要调用构造函数
	X xx;
	xx.X::X(); // 调用构造函数
	// 处理 xx ...
	__result.X::X(xx); // 调用拷贝构造函数
	return;
}
// 调用:
X xx; // 不需要调用构造函数
bar(xx, 0);

开启NRV优化后,即将name return value直接替换为返回对象,少一次拷贝:

void bar(X &__result, int) {
	__result.X::X();
	// 处理 __result ...
	return;
}
注意

NRV优化会导致原本预想中的调用拷贝构造函数变成调用别的构造函数优化,如果拷贝构造函数和别的构造函数提供的功能不同,会出现错误。


测试如下,gcc需要通过-fno-elide-constructors取消NRV优化。


参考

#include 
using namespace std;

class X {
public:
    int a;
    int b;
    X() {
        a=1;
        b=1;
    }
    X (const X& o) {
        cout << "invoke copy constructor" << endl;
        a = o.a;
        b = 0;
    }
    void print() {
        cout << a << ", " << b << endl;
    }
};

X foo() {
    X a;
    a.b = 100;
    a.print();
    return a;
}

int main()
{
    X b = foo();
    b.print();
    return 0;
}
/* 
g++ test.cpp -std=c++11 -fno-elide-constructors 输出:
1, 100
invoke copy constructor
invoke copy constructor
1, 0

g++ test.cpp -std=c++11 输出:
1, 100
1, 100
*/
初始化列表 什么情况下必须使用初始化列表?
  1. 初始化一个引用成员;
  2. 初始化一个常量成员;
  3. 调用基类的有参构造函数
  4. 调用成员的有参构造函数;
注意

1、编译器会将初始化列表按照成员声明顺序(而非初始化列表中的顺序)依次在构造函数中展开(基类构造函数最前),并在用户代码之前。


class X {
private:
	int i;
	int j;
public:
	X(int val)
		: j(val), i(j) // 依赖关系有问题,其实是i先被初始化,此时j的值未知!
	{
	}
}
资料

C++11移动构造/赋值函数什么情况下会自动合成

  • The move constructor is auto-generated if there is no user-declared copy constructor, copy assignment operator or destructor, and if the generated move constructor is valid (§12.8/10).
  • The move assignment operator is auto-generated if there is no user-declared copy constructor, copy assignment operator or destructor, and if the generated move assignment operator is valid (e.g. if it wouldn’t need to assign constant members) (§12.8/21).

C++11列表初始化

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/564213.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-03
下一篇 2022-04-03

发表评论

登录后才能评论

评论列表(0条)

保存