复制构造函数

💻笔者在学习OOP时,看到一个讲C++复制构造函数在什么情况下会执行的代码片段,如下:

class Student {
   public:
    Student(){};
    Student returnS(Student s) { return s; }
    Student(const Student &e) { cout << "Copy Constructure\n"; }
    ~Student() { cout << "Destructure\n"; }
};
int main() {
    Student stu1;
    stu1.returnS(stu1);
    return 0;
}

于是乎,笔者在电脑上敲了一边,确实按照预期复制构造函数执行了两次,第一次在构造形参对象时执行,第二次在返回值复制到主函数产生临时对象时执行。

接着笔者将主函数修改为

int main() {
    Student stu1;
    Student stu2 = stu1.returnS(stu1);
    return 0;
}

心想:不出意外在构造stu2时,会再执行一次复制构造函数,然而当笔者看到运行结果后,发现并非如此,复制构造函数还是执行了两次,于是笔者陷入了大思考。
想到可能是聪明的编译器是不是帮我优化掉了临时对象,直接复制构造了stu2。导致了上面的代码经过优化之后和这样写其实是一样的:

int main() {
    Student stu1;
    Student &&stu2 = stu1.returnS(stu1);
    return 0;
}

右值引用
C++中,引用(reference)是指绑定到内存中的相应对象上。左值引用是绑定到左值对象上;右值引用是绑定到临时对象上。这里的左值对象是指可以通过取地址&运算符得到该对象的内存地址;而临时对象是不能用取地址&运算符获取到对象的内存地址。

于是经过几番搜索,找到了这个东西:

-fno-elide-constructors

The C++ standard allows an implementation to omit creating a temporary that is only used to initialize another object of the same type. Specifying this option disables that optimization, and forces G++ to call the copy constructor in all cases.

果然不出所料,当笔者加上这个参数之后

g++ test_ref.cpp -o test -fno-elide-constructors

一切都对劲了起来😄!

移动构造函数

使用移动构造函数可以提高内存资源的利用效率,从而改进程序的执行性能。

示例程序

#include <bits/stdc++.h>
using namespace std;

class A {
public:
    int *i;
    A(int _i) : i(new int(_i)) {
        cout << "A(int) " << i << '\n';
    }
    ~A() {
        cout << "delete pointer: " << i << '\n';
        delete i;
    }
    // 复制构造函数
    A(const A &a) : i(new int(*a.i)) {
        cout << "A(const A&) " << i << '\n';
    }
    // 移动构造函数
    A(A &&a) : i(new int(*a.i)) {
        cout << "A(const A&&) " << i << '\n';
        a.i = nullptr;
    }
};

A getA(A para) {
    A tmp(2);
    cout << hex << tmp.i << '\n';
    return tmp;
}

int main() {
    A a(1);
    A b = getA(a);
    cout << a.i << '\n';
    return 0;
}

运行结果

A(int) 0x8016eb0            构造a对象
A(const A&) 0x80172e0       参数复制构造para
A(int) 0x8017300            构造tmp对象
0x8017300
A(const A&&) 0x8017320      调用移动构造函数(临时对象构造返回值)
delete pointer: 0           析构临时对象
A(const A&&) 0x8017340      调用移动构造函数(返回值移动到b对象)
delete pointer: 0           析构临时对象(指返回值)
delete pointer: 0x80172e0   析构函数para
0x8016eb0               
delete pointer: 0x8017340   析构b对象
delete pointer: 0x8016eb0   析构a对象