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函数,应该将共同机能放入到第三个函数中,然后让两个共同调用。