有趣的问题

  • 下面的程序输出什么?为什么?
class Test
{
private:
    int mi;
public:
    Test(int i)
    {
        mi = i;
    }
    Test()
    {
        Test(0);           // 注意这里!
    }
    void print()
    {
        printf("mi = %d\n", mi);
    }
};

==>

int main()
{
    Test t;
    
    t.print();
    
    return 0;
}

实例分析: 有趣的问题

#include <stdio.h>

class Test
{
private:
    int mi;
public:
    Test(int i)
    {
        mi = i;
    }
    Test()
    {
        Test(0);
    }
    void print()
    {
        printf("mi = %d\n", mi);
    }
};

int main()
{
    Test t;
    
    t.print();
    
    return 0;
}
输出:
mi = 11341812

发生了什么

  • 程序意图 : 【代码复用】

    • 在 Test() 中以 0 作为参数调用 Test(int i)
    • 将成员变量 mi 的初始值设置为 0
  • 运行结果

    • 成员变量 mi 的值为随机值

究竟哪个地方出了问题?

思考

  • 构造函数是一个特殊的函数

    • 是否可以直接调用?
    • 是否可以在构造函数中调用构造函数?
    • 直接调用构造函数的行为是什么?

答案

  • 直接调用构造函数将产生一个临时对象
  • 临时对象的生命周期只有一条语句的时间
  • 临时对象的作用域只在一条语句中
  • 临时对象是C++中值得警惕的灰色地带

编程实验: 解决方案

定义私有的的可复用普通成员函数

#include <stdio.h>

class Test
{
private:
    int mi;
    
    void init(int i)
    {
        // do other something ...
        
        mi = i;
    }
public:
    Test(int i)
    {
        init(i);
    }
    Test()
    {
        init(0);
    }
    void print()
    {
        printf("mi = %d\n", mi);
    }
};

int main()
{
    Test t;
    
    t.print();

    return 0;
}
输出:
mi = 0

编译器的行为

  • 现代 C++ 编译器在不影响最终执行结果的前提下,会尽力减少临时对象的产生!!!

编程实验: 神秘的临时对象

#include <stdio.h>

class Test
{
private:
    int mi;
public:
    Test(int i)
    {
        printf("Test(int i) : %d\n", i);
        mi = i;
    }
    Test(const Test& t)
    {
        printf("Test(const Test& t) : %d\n", t.mi);
        mi = t.mi;
    }
    Test()
    {
        printf("Test()\n");
        mi = 0;
    }
    void print()
    {
        printf("mi = %d\n", mi);
    }
    ~Test()
    {
        printf("~Test()\n");
    }
};

Test func()
{
    return Test(20);
}

int main()
{
    Test t = Test(10);  // ==> Test t = 10;
    Test tt = func();   // ==> Test tt = Test(20); ==> Test tt = 20;
    
    t.print();
    tt.print();

    return 0;
}
输出:【g++】
Test(int i) : 10
Test(int i) : 20
mi = 10
mi = 20
~Test()
~Test()

分析:
1. Test t = Test(10); 为什么拷贝构造函数没有被调用呢?

在古老的编译器中,会发生如下过程:
a. 生成临时对象(调用构造函数)
b. 用临时对象初始化 t (调用拷贝构造函数)

2. Test tt = func(); 为什么拷贝构造函数没有被调用呢?

在古老的编译器中,会发生如下过程:
a. 生成临时对象(调用构造函数)
b. 返回临时对象,初始化 tt (调用拷贝构造函数)

使用临时对象初始化对象,将额外产生一次拷贝函数的调用,C++ 编译器为了避免性能损失,在不影响最终执行结果的前提下,尽力减少临时对象的产生。
Test t = Test(10);  ==> Test t = 10;
Test tt = func();   ==> Test tt = Test(20); ==> Test tt = 20;

小结

  • 直接调用构造函数将产生一个临时对象
  • 临时对象是性能的瓶颈,也是 bug 的来源之一
  • 现代 C++ 编译器会尽力避开临时对象
  • 实际工程开发中需要人为的避开临时对象

    • 设计时有意识避开
    • 编码时有意识避开 Test t = Test(10); ==> Test t = 10;

以上内容参考狄泰软件学院系列课程,请大家保护原创!


TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧