举个例子:需要注意的是,当类中拥有指针类型的成员变量时,拷贝构造函数中需要以深拷贝(而非浅拷贝)的方式复制该指针成员。有关深拷贝和浅拷贝以及它们的区别,读者可阅读《C++深拷贝和浅拷贝》一文做详细了解。
#include <iostream>
using namespace std;
class demo{
public:
demo():num(new int(0)){
cout<<"construct!"<<endl;
}
//拷贝构造函数
demo(const demo &d):num(new int(*d.num)){
cout<<"copy construct!"<<endl;
}
~demo(){
cout<<"class destruct!"<<endl;
}
private:
int *num;
};
demo get_demo(){
return demo();
}
int main(){
demo a = get_demo();
return 0;
}
可以看到,程序中定义了一个可返回 demo 对象的 get_demo() 函数,用于在 main() 主函数中初始化 a 对象,其整个初始化的流程包含以下几个阶段:如上所示,我们为 demo 类自定义了一个拷贝构造函数。该函数在拷贝 d.num 指针成员时,必须采用深拷贝的方式,即拷贝该指针成员本身的同时,还要拷贝指针指向的内存资源。否则一旦多个对象中的指针成员指向同一块堆空间,这些对象析构时就会对该空间释放多次,这是不允许的。
construct!
class destruct!
g++ demo.cpp -fno-elide-constructors命令运行(其中 demo.cpp 是程序文件的名称),就可以看到完整的输出结果:
construct! <-- 执行 demo()
copy construct! <-- 执行 return demo()
class destruct! <-- 销毁 demo() 产生的匿名对象
copy construct! <-- 执行 a = get_demo()
class destruct! <-- 销毁 get_demo() 返回的临时对象
class destruct! <-- 销毁 a
那么当类中包含指针类型的成员变量,使用其它对象来初始化同类对象时,怎样才能避免深拷贝导致的效率问题呢?C++11 标准引入了解决方案,该标准中引入了右值引用的语法,借助它可以实现移动语义。事实上,此问题一直存留在以 C++ 98/03 标准编写的 C++ 程序中。由于临时变量的产生、销毁以及发生的拷贝操作本身就是很隐晦的(编译器对这些过程做了专门的优化),且并不会影响程序的正确性,因此很少进入程序员的视野。
例如,下面程序对 demo 类进行了修改:事实上,对于程序执行过程中产生的临时对象,往往只用于传递数据(没有其它的用处),并且会很快会被销毁。因此在使用临时对象初始化新对象时,我们可以将其包含的指针成员指向的内存资源直接移给新对象所有,无需再新拷贝一份,这大大提高了初始化的执行效率。
#include <iostream>
using namespace std;
class demo{
public:
demo():num(new int(0)){
cout<<"construct!"<<endl;
}
demo(const demo &d):num(new int(*d.num)){
cout<<"copy construct!"<<endl;
}
//添加移动构造函数
demo(demo &&d):num(d.num){
d.num = NULL;
cout<<"move construct!"<<endl;
}
~demo(){
cout<<"class destruct!"<<endl;
}
private:
int *num;
};
demo get_demo(){
return demo();
}
int main(){
demo a = get_demo();
return 0;
}
可以看到,在之前 demo 类的基础上,我们又手动为其添加了一个构造函数。和其它构造函数不同,此构造函数使用右值引用形式的参数,又称为移动构造函数。并且在此构造函数中,num 指针变量采用的是浅拷贝的复制方式,同时在函数内部重置了 d.num,有效避免了“同一块对空间被释放多次”情况的发生。g++ demo.cpp -o demo.exe -std=c++0x -fno-elide-constructors命令执行此程序,输出结果为:
construct!
move construct!
class destruct!
move construct!
class destruct!
class destruct!
在实际开发中,通常在类中自定义移动构造函数的同时,会再为其自定义一个适当的拷贝构造函数,由此当用户利用右值初始化类对象时,会调用移动构造函数;使用左值(非右值)初始化类对象时,会调用拷贝构造函数。
有关 std::move() 函数的用法,感兴趣的读者可以继续阅读《C++11 move()函数:将左值强制转换为右值》一节。
版权说明:Copyright © 广州松河信息科技有限公司 2005-2025 版权所有 粤ICP备16019765号
广州松河信息科技有限公司 版权所有