欢乐C++ —— 5. 类的初始化

坚强是说给别人听的谎言 提交于 2020-01-10 11:16:08

本文测试内容大部分都是参考该博文 https://www.cnblogs.com/cposture/p/4925736.html

该博文作者深入汇编,写的十分详细,感谢作者大大。

之所以挪一次,是想精简一下,并且补充一些东西。

简述

先简单看看直接初始化和拷贝初始化的定义,然后再详细试验一下不同的拷贝初始化场景会调用哪种拷贝函数。实验环境 vs2017 已关闭编译器优化。

直接初始化和拷贝初始化

类的初始化分为直接初始化和拷贝初始化。

  • 直接初始化就是在定义对象时不使用 = 号的初始化方式。调用普通构造函数。

    string s1("123");
    string s2(s1);

    在c++11中,容器调用 emplace 成员函数创建的元素也进行直接初始化。

  • 而拷贝初始化分很多情况,将会在以下情况发生。

    • 在用 = 定义变量时。
    • 函数调用时将 实参对象 传给 非引用的形参对象。
    • 函数返回时返回非引用类型的对象。
    • 用花括号列表初始化一个数组中的元素或聚合类中的成员。
    • 某些类的某些函数也会拷贝初始化,例如容器的 insert 和 push 成员。

    需要注意的是

    • 拷贝初始化将跟据不同的情况调用不同的拷贝构造函数。
    • 直接初始化有时会调用拷贝构造函数。例如 String s3(s1);
    • 拷贝初始化过程中类类型的隐式转化不可忽略。许多时候,比较奇怪的语法往往是类类型隐式转化造成。

开始实验

#include <iostream>  
#include <cstring>  
using namespace std;
class ClassTest {
public:
    ClassTest( ) {
        c[0] = '\0';
        cout << "ClassTest()" << endl;
    }
    ClassTest& operator=(const ClassTest &ct)   //赋值运算符
    {
        strcpy(c, ct.c);
        cout << "ClassTest& operator=(const ClassTest &ct)" << endl;
        return *this;
    }
    ClassTest(ClassTest&& ct)           //移动构造函数
    {
        cout << "ClassTest(ClassTest&& ct)" << endl;
    }
    ClassTest & operator=(ClassTest&& ct)   //移动赋值运算符
    {
        strcpy(c, ct.c);
        cout << "ClassTest & operator=(ClassTest&& ct)" << endl;
        return *this;
    }
    ClassTest(const char *pc)   //普通构造函数
    {
        strcpy(c, pc);
        cout << "ClassTest (const char *pc)" << endl;
    }
    ClassTest(const ClassTest& ct) //拷贝构造函数
    {
        strcpy(c, ct.c);
        cout << "ClassTest(const ClassTest& ct)" << endl;
    }
private:
    char c[256];
};
ClassTest f1( ) {
    ClassTest c;
    return c;
}
ClassTest f2( ) {
    return ClassTest( );
}
void f3(ClassTest ct) {
    ;
}
int main( ) {
    ClassTest ct1("ab");//直接初始化  
    ClassTest ct2 = "ab";//复制初始化  
    ClassTest ct3 = ct1;//复制初始化  
    cout << "-------3" << endl;
    ClassTest ct4(ct1);//直接初始化  
    ClassTest ct5 = ClassTest("ab");//复制初始化  
    ClassTest ct6 = f1( );
    cout << "-------6" << endl;
    f1( );
    cout << "-------1" << endl;
    f2( );
    cout << "-------2" << endl;
    f3(ct1);
    return 0;
}

image-20200109195808780

下面一句一句解析这个结果。

1. ClassTest ct1("ab");

这句和我们预期一样,直接调用相应的普通构造函数。

2. ClassTest ct2 = "ab";

这句本应该是由 “ab” 实参隐式构造一个临时对象,然后通过该临时对象调用移动构造函数构造ct2。但实际上被编译器优化成1的方式。即临时对象被编译器优化掉了

3. ClassTest ct3 = ct1;
4. ClassTest ct4 (ct1);

4 与 5 都与我们预期相符,调用拷贝构造函数。

5. ClassTest ct5 = ClassTest(“ab”);
6. ClassTest ct6 (ClassTest(“ab”));

5 和 6 本应都是由 “ab” 实参构造一个临时对象,然后通过该临时对象调用移动构造函数构造 ct5 和ct6。但实际上被编译器优化成1的方式。即临时对象被编译器优化掉了

与 2 不同的是,这块是显式生成临时对象,如果把构造函数 explicit 声明也没有影响,而2将编译不通过。

7. ClassTest ct7 = f1();
8. f1();

7 和 8 结果有点出乎意料,实际上这两个过程都是相同的,区别在于调用 f1() 时,7 是将ct7的地址传给函数,而 8 是将主函数栈上的临时对象的地址传入函数内。在函数内都先为局部对象调用默认构造函数,然后将这个局部对象作为实参分别调用相应的移动构造函数构造 ct7 和临时量。

9. f2();

按照7 和8 的分析,这个不难看出,是将一个主函数栈上临时对象的地址传进函数内,在函数结尾处默认构造这个临时对象。

10. f3(ct1);

真的就是普通的拷贝构造函数。

总结

通过上面实验过程,我们可以发现,在编译器能不用临时量就不用临时量,如2 ,5,6。

如果看不过瘾可以看看原作者的详细博文 。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!