【EffectiveC++】构造/析构/赋值运算-下篇

文章讨论了C++编程中的几个关键点:不应在构造和析构函数中调用virtual函数,以避免未初始化的成员问题;operator=应返回对*this的引用以支持链式赋值;处理自我赋值情况以确保操作安全;复制对象时务必复制所有成员变量,以保持一致性。这些都是编写稳健C++代码的重要原则。
摘要由CSDN通过智能技术生成

条款09:绝不在构造和析构过程中调用virtual函数

假设你有个class继承体系,用来塑膜股市交易如买进、卖出的订单等等。这样的交易一定要经过审计,所以每当创建一个交易对象,在审计日志中也需要创建一笔适当记录。下面是一个看起来颇为合理的做法:
class Transaction{ //所以交易的Base class
public:
Transaction();
virtual void log transacton() const=0;//做出一份因类型不同而不同的日志记录(log entry)

};
Transaction::Transaction()//Base class构造函数之实现
{

logTransaction();
}
class BuyTranaction:public Transaction{//derived class
public:
virtual void logTransaction() const;//忘记(log)此型交易

};
class sellTransaction:public Transaction{//derived class
public:
virtual void logTransaction() const;//忘记(log)此型交易

};
BuyTransaction b;
无疑地会有一个BuyTransaction构造函数被调用,但首先Transaction构造函数一定会更早被调用;sde1,derived class对象的Base class成分会在derived class自身成分被构造之前先构造妥当。Transaction构造函数的最后一行调用virtual函数logTransaction,这正是引发惊奇的起点。这时候调用的logTransaction是Transaction内的版本,不是BuyTransaction内的版本-即使目前即将建立的对象类型是BuyTransaction。是的,Base class 构造期间virtual函数绝不会下降到derived classes阶乘。取而代之的是,对象的作为就像隶属Base类型一样。非正式的说法或许比较传神:在Base class构造期间,virtual函数不是virtual函数。

这一似乎反直觉的行为有个好理由。由于base class 构造函数的执行更早于derived class构造函数,当Base class构造函数执行derived class阶层,要知道derived class的函数几乎必然取用local成员变量,而那些成员变量尚未初始化。这将是一张通往不明确行为和彻夜调试大会的直达车票。“要求使用对象内部尚未初始化的成分”是危险的代名词,所以C++不会让你走这条路。

注意:在构造和析构期间不要调用vitual函数,因为这类函数调用从不下降至derived class

条款10:令operator=返回一个reference to*this

关于赋值,有趣的是你可以把它们写成连锁形式:
int x,y,z;
x=y=z=15;//赋值连锁形式
同样有趣的是,赋值采用右结合律,所以上述连锁被赋值解析为:
x=(y=(z=15));
这里的15先被赋值给z,然后其结果(更新后的z)再赋值给y,然后其结果(更新后的y)再赋值给x.
为实现“连锁赋值”,赋值操作符必须返回一个reference指向操作符的左侧实参。这是你为class实现赋值操作符应该遵守的协议:
class widget{
public:

widget& operator=(const widget& rhs)//返回类型是个reference(引用)指向当前对象
{

return* this;//返回左侧对象
}

};
这个协议不仅适用于以上的标准赋值形式,也适用于所有赋值相关运算,例如:
class widget
{
public:

widget& operator+=(const widget& rhs)//这个协议适用于+=,-=,*=,等等
{

return *this;
}
widget& operator=(int rhs)//此函数也适用,即使此操作符的参数类型不符合协定
{

return this;
}

};
注意,这只是个协议,并无强制性。如果不遵循它,代码一样可以通过翻译。然而这协议被所有内置类型和标准程序库提供的类型如 string,vectory,complex,tr1::shared_ptr或即将提供的类型共同遵守。因此除非你有一个标新立异的好理由,不然还是随众吧。
请记住:
令赋值(assignment)操作符返回一个reference to
this。

条款11:在operator=中处理“自我赋值”

"自我赋值”发生在对象被赋值给自己时:
class widget{ …};
widget w;

w=w//赋值给自己
这个看起来有点愚蠢,但它合法,所以不要认定客户绝不会那么做。此外赋值动作并不总是那么可被一眼辨识出来,例如:
a[i]=a[j];//潜在的自我赋值
如果i和j有相同的值,这个便是自我赋值。再看:
px=py;//潜在的自我赋值
如果px和py恰巧指向同一个东西,这就是自我赋值。这些并不明显的自我赋值,是“别名”(aliasing)带来的结果:所谓"别名”就是“有一个以上的方法指称某对象”。一般而言如果某段代码操作pointer或references而被它们用来“指向多个相同类型的对象”,就需要考虑这些对象是否为同一个继承体系,他们甚至不需要声明为相同类型就可能造成“别名”,因为一个base class的reference或pointer可以指向一个derived class对象:
class Base{…};
class Derived:public Base{…};
void doSomething(const Base& rb,Derived
pd);//rb和
rd有可能其实是同一对象
请记住:
1.确保当对象自我赋值时operator有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
2.确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确

条款12:复制对象勿忘其每一个成分

设计良好之面向对象系统会将对象的内部封装起来,只留两个函数负责对象拷贝,那便是带着适切名称的copy构造函数和copy assigment操作符,我称他们为copying函数,并说明这些“编译器生成版”的行为:将被拷贝对象的所有成员变量做一份拷贝。
如果你声明自己的copying函数,意思就是告诉编译器你并不喜欢缺省实现中的某些行为。编译器仿佛被冒犯似的,会以一种奇怪的方式回敬:当你的代码几乎必然出错时却不告诉你。
考虑一个class用来表现顾客,其中手工写出(而非由编译器创建)copying函数,使得外界对他们的调用会被志记下来:
void logCall(const std::string& funcName);//制造一个log entry
class Customer{
public:

Customer(const Curtomer& rhs);
Customer& operator=(const Customer& rhs);

private:
std::string name;
};
Customer::Customer(&rhs)
:name(rhs.name)//复制rhs的数据
{
logCall(“Customer copy constructor”);
}
Customer& (“Customer:: copy assignment operator”);
name=rhs.name;
return *this;//复制rhs的数据
}
这里的每一件事情看起来都很好,而实际上每件事情也的确很好,直到另一个成员变量加入战局:
class Date{…}//日期
class Customer{
public:

private://同前
std::string name;
Date lastTransation;
};
这时候既有的copying函数执行的是局部拷贝:他们的确复制了顾客的name,但没有复制新添加的lastTransaction。大多数编译器对此不出任何怨言-即使在最高警告级别中。这是编译器对“你自己写出的copying函数”的复仇行为:既然你拒绝它们为你写出copying函数,如果你的代码不完全,它们也不告诉你。结论很明显:如果你为class添加一个成员变量,你必须同时修改copying函数。(你也需要修改class的所有构造函数以及任何形式的operator=)。如果你忘记,编译器不大可能提醒你。
请记住:
1.copying函数应该确保复制“对象的所有成员变量”及“所有base class成分”。
2.不要尝试以某个copying函数实现另一个copying函数。应该共同机能放进第三个函数中,并由两个copying函数共同调用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神之子-小佳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值