拷贝构造
书曰,拷贝构造在下面三种情况下发生:
1.用一个对象初始化另一个对象
class A{}; A a; A b(a); A b = a;2.以对象对函数做值传参
3.函数返回对象
临时对象
为什么研究临时对象?
主要是为了提高程序的性能以及效率,因为临时对象的构造与析构对系统开销也是不小的,所以我们应该去了解它们,知道它们如何造成,从而尽可能去避免它们。
建立一个没有命名的非堆对象会产生临时对象。这种未命名的对象通常在三种条件下产生:为了使函数成功调用而进行隐式类型转换时候、传递函数参数和函数返回对象时候。
拷贝构造的一些细节:
1.对于一个类X,如果一个构造函数的第一个参数是下列之一,且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数
a> X&
b> const X&
c> volatile X&
d> const volatile X&
2.类中可以存在超过一个拷贝构造函数
但是,如果一个类中只存在一个参数为X&的拷贝构造,那么就不能用const X或volatile X的对象实行拷贝构造初始化
3.如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认拷贝构造函数,参数可能是X::X(const X&)或X:X(X&),由编译器根据上下文选择哪一个
默认拷贝构造函数的行为:
跟构造函数一样,执行先父类后子类的构造
对类中每个数据成员执行成员拷贝(memberwise copy):
a>如果数据成员是某个类的实例,那么调用此类的拷贝构造函数
b>如果数据成员是一个数组,对数组的每一个执行按位拷贝(bitwise copy)
c>如果数据成员是一个数量(应该是指简单数据类型吧),如int、double,那么调用系统内建的赋值运算符对其赋值
PS: 其实编译器是否真的生成默认拷贝构造函数的代码取决于拷贝构造是否具有必要性(nontrivial),并非所有类都会合成默认拷贝构造的代码。当该类具备位拷贝属性(bitwise copy semantics)时合成的拷贝构造是trivial的,此时合成拷贝构造代码是没必要的,编译器会选择bitwise copy,而不是memberwise copy:
a>类内含一个成员变量是类实例,而后者有设计者定义的拷贝构造或编译器为其合成了拷贝构造(nontrivial)
b>类继承自一个有拷贝构造的父类,后者的拷贝构造是设计者定义或编译器为其合成的(nontrivial)
c>类声明有virtual函数
d>类的父类串链中有virtual基类
4.拷贝构造函数不能由成员函数模板生成:
成员函数模板不改变语言的规则,而语言的规则说,如果程序需要一个拷贝构造函数而你没有声明它,那么编译器会为你自动生成一个。所以成员函数模板并不会阻止编译器生成拷贝构造函数,赋值运算符重载也遵循同样的规则。
下面贴一个比较费解的程序:
#include <stdio.h> #include <iostream> #include <string.h> using namespace std; class Internet { public: Internet() { printf("entered Internet::Internet(). addr(this) = 0x%lx.\n",(long)this); }; Internet(const char *name) { printf("entered Internet::Internet(const char*). addr(this) = 0x%lx.\n", (long)this); strcpy(this->name,name); } Internet(const Internet& temp) { printf("entered Internet::Internet(const Internet& temp). addr(this) = 0x%lx. addr(temp) = 0x%lx.\n", (long)this, (long)&temp); memset(this->name, 0, 20); } Internet& operator =(const Internet &temp) { cout << "entered Internet& operator =(const Internet& temp)" << endl; } ~Internet() { printf("entered Internet::~Internet(). addr(this) = 0x%lx.\n", (long)this); } char name[20]; }; Internet tp() { Internet b("xiarendeniao"); cout << "b construct completed" << endl; return b; } int main(int argc, char** argv) { Internet a(tp()); cout << "a construct completed" << endl; cout << a.name << endl; //注意,拷贝构造把name清空了,这里还是会有"xiarendeniao"输出,说明实质没有进入拷贝构造函数 cout << endl << "new test" << endl; Internet x("xiarendeniao"); Internet y(x); cout << y.name << endl; //注意,这里输出为空就表示确实进入了拷贝构造 return 0; }
结果:
xudongsong@HP:~/c++_study$ g++ -o copy_constructor copy_constructor.cpp ; ./copy_constructor entered Internet::Internet(const char*). addr(this) = 0x7fff0b307830. b construct completed a construct completed xiarendeniao new test entered Internet::Internet(const char*). addr(this) = 0x7fff0b307850. entered Internet::Internet(const Internet& temp). addr(this) = 0x7fff0b307870. addr(temp) = 0x7fff0b307850. entered Internet::~Internet(). addr(this) = 0x7fff0b307870. entered Internet::~Internet(). addr(this) = 0x7fff0b307850. entered Internet::~Internet(). addr(this) = 0x7fff0b307830.
加上const之后,程序输出结果却表明对a的构造过程又没有调用拷贝构造函数
我个人理解如下:编译时编译器做拷贝构造的函数检查,tp()需要返回一个const临时变量用于对a作拷贝构造,所以要求有const Internet&为参的拷贝构造函数
编译后期通过优化又把临时变量和拷贝构造的过程都简化掉了
那么,问题出现了,如果我就是需要在Internet(const Internet& temp)这个拷贝构造里面对对象的数据做一些操作比如深拷贝呢?岂不是无法实现?期待近期能遇到高人为我讲解一下
其实,我本人很少让拷贝构造发生,也尽力回避这种隐晦的东西。对象实例都是一个个独立的个体,拿一个个体初始化另一个个体很奇怪嘛(当然不排除这种需求的存在)。
虚拟继承
当多重继承时,多个父类是从同一个类继承下来的,编译会报错,因为父类的可能有从同一个类继承来的方法,编译器不知道具体调用的是哪一个
这时需要用虚拟继承来解决,虚拟继承保证从你虚拟继承的父类那里只得到一份数据
http://wenku.baidu.com/view/7fc1d5ec856a561252d36fee.html
这个写很好啊,从语法、语义、模型、性能、引用多方面进行了比较深的分析,http://www.cppblog.com/chemz/archive/2007/06/12/26135.html
下面是代码验证:
#include <iostream> using namespace std; class Vehicle //交通工具 { public: Vehicle(int weight = 0) { this->weight = weight; } void SetWeight(int weight) { cout << "Vehicle::SetWeight()设置重量" << endl; this->weight = weight; } //virtual void ShowMe() = 0; protected: int weight; }; class Car : public Vehicle //小汽车 //class Car : public virtual Vehicle //小汽车 { public: Car(int weight = 0, int aird = 0) : Vehicle(weight), aird(aird) {}; //void ShowMe() //{ // cout << "我是小汽车" << endl; //} protected: int aird; }; class Boat : public Vehicle //轮船 //class Boat : public virtual Vehicle //轮船 { public: Boat(int weight = 0, float tonnage = 0) : Vehicle(weight), tonnage(tonnage) {}; //void ShowMe() //{ // cout << "我是轮船" << endl; //} protected: float tonnage; }; class Amphibiancar : public Car, public Boat //水陆两用汽车 { public: Amphibiancar(int weight = 0, int aird = 0, float tonnage = 0) : Car(weight,aird), Boat(weight,tonnage) {}; //void ShowMe() //{ // cout << "我是水陆两用汽车" << endl; //} }; int main(int argc, char** argv) { Amphibiancar a(10,10,34.9f); a.SetWeight(3); //a.ShowMe(); //Vehicle* v = new Amphibiancar(45,45,34.8); //v->ShowMe(); }
xudongsong@HP:~/c++_study$ g++ -o virtual_inherit virtual_inherit.cpp virtual_inherit.cpp: 在函数‘int main(int, char**)’中: virtual_inherit.cpp:70:4: 错误: 对成员‘SetWeight’的请求有歧义 virtual_inherit.cpp:12:7: 错误: 备选为: void Vehicle::SetWeight(int) virtual_inherit.cpp:12:7: 错误: void Vehicle::SetWeight(int)
重载、覆盖、隐藏 参考《高质量C++编程》 http://man.chinaunix.net/develop/c&c++/c/c.htm#_Toc520634042
重载
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。
覆盖---指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字。
隐藏--- 派生类的函数屏蔽了与其同名的基类函数(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
说白了,分别用父类指针bPtr和子类指针dPtr指向一个子类对象:
一般来讲,bPtr调用的函数都是父类的函数、dPtr调用的函数都是子类函数;
如果bPtr想调用子类函数(多态、覆盖)需要父子函数同名参数也相同、父类函数还得有virtual关键字;
子类的函数把父类同名不同参数的所有函数隐藏掉(dPtr不能调用从父类继承来的与自身函数同名不同参数的所有函数),子类的函数把父类同名同参数但是没有virtual关键字的函数也隐藏掉(对于有virtual的就是覆盖和多态的概念了)。