class Transaction { public: Transaction(void){ logTransaction(); } virtual ~Transaction(void){} virtual void logTransaction() const = 0; }; class BuyTransaction : public Transaction{ public: BuyTransaction(void){} ~BuyTransaction(void){} void logTransaction() const{ std::cout << "BuyTransaction::logTransaction()" << std::endl; } };
客户端代码:安全
BuyTransaction b;
程序报错ide
从错误中就能够看出,Transaction的构造函数并无调用子类的virtual方法logTransaction(),而是调用自己的,可是自身的logTransaction()初纯虚函数,并无实现,因此程序保存。固然,咱们能够实现Transaction的纯虚函数logTransaction(),来进一步验证,虽然这么作没意义函数
能够看出,确实是调用基类Transaction自己的构造函数。this
根据多态的定义,当子类重载基类的virtual成员方法,应该调用子类的重载方法。可是这里有个前提,子类对象必须存在!若是连对象都不存在,那对象的成员变量也就不存在,若是成员方法要访问成员变量,访问什么?spa
本例中,是在基类的构造函数中调用virtual方法。构造子类对象的顺序是,从基类到子类,依次调用构造函数。实际上,在调用基类的构造函数时,压根就不知道子类对象的存在。这时的对象实际上就是基类对象,而virtual函数天然被编译器解析至基类的virtual函数。3d
先来看返回引用指针
class Widget{ public: Widget(void); Widget(int value); ~Widget(void); Widget& operator=(const Widget& rhs); void log(); private: int mVaule; }; Widget::Widget(void) : mVaule(0){} Widget::Widget( int value ) : mVaule(value){} Widget::~Widget(void){} void Widget::log(){ cout << "Widget = " << mVaule << endl; } Widget& Widget::operator=( const Widget& rhs ){ mVaule = rhs.mVaule; return *this; }
客户端代码:orm
Widget w1 = 1; Widget w2 = 2; Widget w3 = 3; w1 = w2 = w3 = 123; w1.log(); w2.log(); w3.log();
和预期的同样,赋值操做正常执行。xml
那咱们换成返回值,再来看看有何反应对象
Widget Widget::operator=( const Widget& rhs ){ mVaule = rhs.mVaule; return *this; }
同样能够执行,那为何要让operator=返回引用呢?
接下来,咱们分别在拷贝构造函数和拷贝赋值运算符以及析构函数加上打印语句
operator=返回引用
Widget::~Widget(void){ cout << "deconstructor" <<endl; } Widget::Widget( const Widget &rhs ) : mVaule(rhs.mVaule){ cout << "copy constructor" << endl; } Widget& Widget::operator=( const Widget& rhs ){ cout << "Widget copy assignment " << endl; mVaule = rhs.mVaule; return *this; }
operator=返回值
Widget Widget::operator=( const Widget& rhs ){ cout << "Widget copy assignment " << endl; mVaule = rhs.mVaule; return *this; }
对比运行结果,能够清楚的发现,operator=返回值的话,在连续赋值时,会多调用三次拷贝构造函数和三次析构函数
咱们来分析在operator=返回值的状况下
w1 = w2 = w3 = 123;
发生了什么?
首先w3 = 123,这部分调用operator=,注意因为返回的值,在return *this,会调用拷贝构造函数,返回的是w3的拷贝.
而后w2 = w3,首先也是调用operator=,在return *this,返回的是w2的拷贝,再一次调用拷贝构造函数
最后w1 = w2,和上述的状况同样,返回的是w1的拷贝。
赋值操做结束后,w3,w2,w1的拷贝是临时对象,被销毁。因此,调用了三次析构函数。
因此,operator=返回引用,不只是协议,还能够又可避免拷贝构造函数和析构函数的调用。
class SelfAssignment{ public: ... SelfAssignment& operator=(const SelfAssignment& rhs); private: int *mValuePtr; }; SelfAssignment& SelfAssignment::operator=( const SelfAssignment& rhs ){ delete mValuePtr; mValuePtr = new int(*rhs.mValuePtr); return *this; }
一旦客户端这样写
SelfAssignment s; s = s;
s的成员指针已经成为空悬指针。
一个简单的方法,就能够避免1、中出现的问题,复制以前先检查是不是同一个对象
SelfAssignment& SelfAssignment::operator=( const SelfAssignment& rhs ){ if(this == &rhs) return *this; delete mValuePtr; mValuePtr = new int(*rhs.mValuePtr); return *this; }
可是,若是在建立对象时,即new的时候,抛出异常的话,s的成员指针依然是空悬指针。这里就须要异常性检查
SelfAssignment& SelfAssignment::operator=( const SelfAssignment& rhs ){ int *pOrig = mValuePtr; mValuePtr = new int(*rhs.mValuePtr); delete pOrig; return *this; }
实际上,就是在确保成员指针正常复制以前,先不要删除器指向的内存。一旦过程当中出现异常,成员指针还能够指向原来的内存。此外,就算复制的对象是自己,也能够正常运行。
这是《C++ Primer》上的方法,我的以为《Primer》上的方法更容易理解
SelfAssignment& SelfAssignment::operator=( const SelfAssignment& rhs ){ int *newPtr = new int(*rhs.mValuePtr); delete mValuePtr; mValuePtr = newPtr; return *this; }
《Primer》提供的建议是,现将右侧的运算对象拷贝纸一个局部临时对象。当拷贝完成后(异常没有发生),销毁左侧运算对象的现有成员就安全了。一旦左侧运算对象被销毁,剩下的就是数据从临时对象拷贝到左侧对象的成员中。
这项技术用到了标准库的swap()函数
SelfAssignment& SelfAssignment::operator=( const SelfAssignment rhs ){ std::swap(*this, rhs); return *this; }
要注意的就是参数constSelfAssignment rhs是非引用参数,使用的是值传递。swap交换对象自己和参数的成员变量,操做完成以后,rhs的成员变量实际上就是原来对象的成员变量。离开拷贝赋值运算符后,rhs销毁,也就是对象原先的成员变量销毁。
这项技术解决了异常安全和自赋值安全。在传递参数的时候,就已经获得右值的副本。至关于
int*newPtr = new int(*rhs.mValuePtr);
只要参数传递的过程当中没有异常,就解决了异常安全的问题。
同时swap使得对象和副本交换,就算rhs是对象自己,也是和本身的副本交换,销毁的也是副本,这也解决了自我赋值安全的问题。
默认生成的两个负责拷贝的函数,会赋值全部的non-static成员变量。
l内置类型和对象类型:值拷贝
若是是指针类型,只会拷贝指针,不会拷贝指针所指的内存。
若是是引用类型,sorry,必须自定义拷贝函数。
也就是说,编译器生成的拷贝函数是浅拷贝
由于,当自定义的拷贝函数忘记复制某一个成员变量,编译器不会发出任何警告。
这一点,很容易遗漏。可能在自定义拷贝函数的过程当中,只复制了子类自己的成员变量,而遗漏了父类的成员变量。
然而,父类的成员变量每每是private,子类不能直接访问,复制父类的成员变量的方法就是调用对应的父类方法。拷贝构造函数就调用父类的拷贝构造函数,拷贝赋值运算符就调用父类的拷贝赋值运算符。
Copy:
DerivedClass::DerivedClass(const DerivedClass& rhs) : BasedClass(rhs),初始化成员变量{}
Copy assignment:
DerivedClass& DerivedClass::operator=(const DerivedClass& rhs){ BasedClass::operator=(rhs); 复制成员变量 return *this; }
这里指的特殊函数,就是构造函数,析构函数,拷贝构造函数,拷贝赋值运算符。
尤为是拷贝构造函数和拷贝赋值运算符,绝大多数时候,代码是同样的。正确的作法是将相同的代码提取出来,封装成普通的方法,而后让拷贝函数区调用。