构造/析构/赋值运算(二)

构造/析构/赋值运算

Constructors,Destructors,and Assignment Operators

9.绝不在构造和析构过程中调用virtual函数

Never call virtual functions during construction or destruction.
derived class对象内的base class成分会在derived class自身成分被构造之前先构造妥当。

假如base class内含有虚函数,在base class成分构造期间,derived class成分还未初始化。如果此期间调用的virtual函数下降到derived class阶层,derived class的函数几乎必然取用local变量,而那些变量尚未初始化。这是一张通往不明确行为和彻夜调试大会的直达车票。

对待这种情景,最安全的做法认为,对象是在derived class构造函数开始执行前不会成为derived class。

无法使用virtual函数从base class 向下调用,在构造期间,我可以“领derived class将必要的构造信息向上传递至base class构造函数”替换之加以弥补。

#include<iostream>
class Transaction {
public:
	explicit Transaction(const std::string& logInfo);
	void logTransaction(const std::string& logInfo) const;//non-virtual

};
Transaction::Transaction(const std::string& logInfo)
{
	logTransaction(logInfo);//non-virtual调用
}
class BuyTransaction :public Transaction {
public:
	BuyTransaction(/*parameters*/)
		:Transaction(createLogString(/*parameters*/)) //将log信息传递给base class
	{}
private:
	static std::string createLogString(/*parameters*/);//static函数先于对象存在
};
  • 在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造和析构的函数那层)。

10.令operator=返回一个reference to *this

Have assignment operators return to *this.
这是个协议,并非强制性。这样的好处是,方便连续赋值。适用于所有赋值相关运算。

#include<iostream>
class Ration {
public:
	Ration(int val_=0)
		:val(val_)
	{
	}

	Ration& operator=(const Ration& rhs)
	{
		this->val = rhs.val;
		return *this;//这个协议实用于+=、-=、*=等
	}
private:
	int val;
};
int main()
{
	Ration r1(15);
	Ration r2, r3;
	r2 = r3 = r1;//赋值采用右结合律,等同于r2 = (r3 = r1)
}

11.在operator=中处理“自我赋值”

Handle assignment to self in operator=.
可能是自我赋值的场景

a[i]=a[j];
*px=*py;

这些不明显的自我赋值,均是由“别名”带来的结果,所谓“别名”就是“有一个以上的方法指代某对象”,如指针and引用,以及继承中的切片行为带来的结果。

class Base {};
class Derived :public Base {};
void do_some_thing(Base& b, Derived* d);//此时b和d可能指向同一个对象

证同测试

扫描二维码关注公众号,回复: 9301035 查看本文章
class Bitmap{};
class Widget {
private:
	Bitmap* pb;
public:
	Widget& operator=(const Widget& rhs)
	{
		if (&rhs == this)return *this;//证同测试

		delete pb;
		pb = new Bitmap(*rhs.pb);
		return *this;
	}
};

上述传统解决方法仍然不能解决异常方面的麻烦。如果在赋值过程抛出异常,那么对象将指向被删除的Bitmap,这样的指针十分害人。

class Bitmap{};
class Widget {
private:
	Bitmap* pb;
public:
	Widget& operator=(const Widget& rhs)
	{
		Bitmap* pOrig = pb;
		pb = new Bitmap(*rhs.pb);
		delete pOrig;
		return *this;
	}
};

如果“自我赋值”出现的概率很大,那么,最好再加上“证同测试”,以提高效率,否则这样就可以解决问题。
CAS,copy and swap

class Bitmap{};
class Widget {
private:
	void swap(Widget&rhs)//交换两个Widget的pb指向
	{
	}
	Bitmap* pb;
public:
	Widget& operator=(Widget rhs)
	{
		swap(rhs);
		return *this;
	}
};
  • 确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
  • 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

12.复制对象时勿忘其每一个成分

Copy all part of an object
除了保证复制所有的local成员变量.任何时候只要你承担起“为derived class撰写copying函数”的重责大任,必须很小心第也复制其base class成分。那些成分往往是private,所以你无法直接访问她们,你应该让derived class的copying函数调用base class函数。

其实两个copying函数往往拥有相同的实现本体,这可能会引诱你让某个函数去怂恿另一个函数以避免代码重复.这样精益求精的态度值得赞赏,但是令某个copying函数调用一个copying函数却无法让你达到你想要的目标.

令copy assignment操作符调用copy构造函数是不合理的,因为这就像试图构造一个已经存在的对象.这挺起来就很不通顺.单纯的接受这个建议:你不该令copy assignment操作符调用copy构造函数.

令copy构造函数调用copy assignment操作符同样没有意义.构造函数是用来初始化新对象,而assignent操作符只施行于已初始化对象身上.对应尚未构造好的对象赋值,就像在一个尚未初始化的对象身上做"只对已初始化对象才有意义"的事情一样.同样无聊.

如果你发现你的copy构造函数和copy assignment操作符有相近的代码,消除重复代码的做法就是建立一个新的成员函数给两者调用这样的函数往往是private而且常被命名为init. 你可以尝试这样.

  • Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。
  • 不要尝试以某个copying函数实现另一个copying函数。应该将其共同机能放进第三个函数中,并由两个copying函数共同调用。
发布了139 篇原创文章 · 获赞 55 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/Vickers_xiaowei/article/details/103754806