Effective C++阅读记录(part 2 - 构造/析构/赋值运算 tips 9 - 12)

Tips09 - 绝不再构造和析构过程中调用virtual函数

不能在构造函数和析构函数期间调用virtual函数,因为这样的调用不会带来预期的结果。
eg:

class Transaction{
public:
	Transaction();
	virtual void logTransaction() const = 0;

	...
};

Transaction::Transaction()
{
	...
	logTransaction();
}

class BuyTransaction: public Transaction{
public:
	virtual void logTransaction() const;
	...
}

class SellTransaction:public Transaction{
public:
	virtual void logTransaction() const;
	...
}
int main()
{
	BuyTransaction b;
	return 0;
}

问题来源:derived class对象内的base class成分会在derived class自身成分构造之前构造妥当。
在上述代码中,由于在调用BuyTransaction的构造函数之前,会优先调用Transaction的构造函数,但由于此时BuyTransaction类型并未建立,所以会在BuyTransaction的构造函数中调用Transaction的logTransaction。也就是说在base class构造期间,virtual函数不会下降到derived class阶层。
由于无法确保每次一有Transaction继承体系上的对象被创建,就会有适当版本的logTransaction被调用。所以在Transaction构造函数中对着对象调用virtual函数使一种错误做法。
解决方案:在class Transaction 内将logTransaction改为non-virtual,然后要求derived class构造函数传递必要信息给Transaction构造函数,然后这个构造函数便可以安全地调用non-virtual logTransaction。eg:

class Transaction{
public:
	explicit Transaction(const std::string& logInfo);
	void logTransaction(const std::string& logInfo) const;
	
	...
};

Transaction::Transaction()
{
	...
	logTransaction(logInfo);
}

class BuyTransaction: public Transaction{
public:
	BuyTransaction(parameters):Transaction
	(createString(parameters))
	{
	...
	}
}

class SellTransaction:public Transaction{
public:
	SellTransaction(parameters):Transaction
	(createString(parameters))
	{
	...
	}
}

**记住:**在构造和析构期间不要调用virtual函数,因为这类调用从不降至derived class。

Tips10 - 令operator= 返回一个reference to *this

在赋值中可以使用连等形式。eg:

int x, y, z;
x = y = z = 0;

为了实现上述过程,赋值操作符必须返回一个reference指向操作符地左侧实参。eg:

class Widget{
public:
	...
	Widget& operator=(const Widget& rhs)
	{
		...
		return *this;
	}
}

此协议不仅适用于标准赋值,还适用于赋值相关地符号,比如 += -= *= 等符号运算。
**记住:**令赋值操作符返回一个reference to *this。

Tips11 - 在operator= 中处理”自我赋值“

”自我赋值“是指对象被赋值给了自己。eg:

Widget w;
...
w = w;
a[i] = a[j];//i = j地时候
*px = *py;//当px 和py指向同一个东西地时候

如果你尝试自行管理资源(写一个用于资源管理地class),可能会掉进“在停止使用资源之前意外释放了它”地陷阱。假设建立一个class用来保存一个指针指向一款动态分配地位图。eg:

class Bitmap{...}
class Widget{
	...
private:
	Bitmap* pb;
};
//表面上合理,但在自我赋值时并不安全
Widget& Widget:: operator=(const Widget* rhs)
{
	delete pb;
	pb = new Bitmap(*rhs.pb);
	return *this;
}

问题在于:如果是自我赋值地话,delete会同时销毁rhs地pb和本地pb。
简易地解决办法是在删除之前判断是不是自我赋值,如果是就什么都不做。但仍存在一定地问题:如果new Bitmap导致异常(内存不足或者copy构造函数异常),widget会持有一个指向一块删除地bitmap,这样地指针有害,无法安全地删除也无法安全地读取它们。
解决办法: 使用 copy and swap技术。eg:

class Widget{
	...
	void swap(Widget &rhs);
	...
};
Widget& Widget::operator=(const Widget& rhs)
{
	Widget temp(rhs);
	swap(temp);
	return *this;
}

**记住:**确保当对象自我赋值时operator= 有良好地行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的顺序语句、以及copy-and-swap。
确定任何函数如果操作一个以上的对象,而其中多个对象时同一个对象时,其行为仍然正确。

Tips12 - 复制对象时勿忘其每一个成分

由tips5可以知道系统会自动为我们实现copy函数和copy assignment操作符,如果我们需要自己声明自己的copy函数和操作符时,必须要知道:任何时候只要你承担其”为derived class撰写copy函数“的重大责任,必须很小心地复制其base class成分,那些成分往往是private,所以无法直接访问它们,应该让derived class调用copy函数调用相应地baseclass函数。eg:

class Data{...};
class Customer{
public:
	...
	Customer(const Customer& rhs);
	Customer& operator=(const Customer& rhs);
	...
private:
	std::string name;
	//Data lastTransaction;
}

Customer::Customer(const Customer& rhs): name(rhs.name)
{
	logCall("Customer copy constructor");
}

Customer& Customer::operator=(const Customer& rhs)
{
	logCall("Customer copy assignment operator");
	name = rhs.name;
	return this;
}

当我们为该类添加一个Data类型的成员后,在执行copy函数时,系统并不会告诉我们没有复制新添加的lastTransaction。而且编译器也不会报错或警告。所以每添加一个新的成员,就需要修改copy函数。
除此之外,最大的问题是发生继承后:

class PriorityCustomer: public Customer{
public:
	...
	PriorityCustomer(const PriorityCustomer&rhs);
	PriorityCustomer& operator=(const PriorityCustomer& rhs);
	...
private;
	int priority;
}

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
	:priority(rhs.priority)
{};
PriorityCustomer& PriorityCustomer::
	operator=(const PriorityCustomer& rhs)
{
	priority = rhs.priority;
	return *this;
}

本处代码的问题在于PriorityCustomer的copy函数只复制了它所声明的变量,并未复制它继承于Customer的成员变量。而且这些成员变量还是私有的,不能够直接访问。
解决办法:让derived class的copy函数调用相应的base class 函数:

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
	:Customer(rhs), priority(rhs.priority)
{};
PriorityCustomer& PriorityCustomer::
	operator=(const PriorityCustomer& rhs)
{
	Customer::operator=(rhs);
	priority = rhs.priority;
	return *this;
}

复制每一个成分是指:当编写一个copy函数,请确保(1)复制所有的local成员变量,(2)调用所有base classes内的适当的copying函数。
需要注意的是:不能为了避免代码重复,就让copy函数去调用copy assignment操作符,同理反之亦然。
如果发现copy构造函数和copy assignment操作符有近似的代码,消除重复的办法通常是:定义一个私有的init函数,然后给它们调用。

记住: copying函数应该确保复制“对象内的所有成员变量”及“所有base class 成分”;不要尝试某个copy函数实现另一个copy函数,应该将共同机能放入到第三个函数中,然后让两个共同调用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值