Effective C++(2)(5~12)
05:了解C++默默编写并调用了哪些函数
如果定义一个类
class Empty{};
相当于定义了
class Empty{
public:
Empty(){}; //default构造
Empty(const Empty& rhs){}; //拷贝构造
~Empty(); //编译器合成的析构函数
Empty& operator=(const Empty& rhs){};//拷贝赋值运算符
};
由于这本书标准不是C++11,有关默认移动构造和移动赋值函数等我了解了之后再补充
如果定义了带有参数的构造函数,则不再合成默认构造函数,但可以显式要求编译器合成
Empty() = default;
当类内含有引用成员或cont的成员时,编译器拒绝生成默认拷贝赋值运算符。
如果某个基类的拷贝赋值运算符为private,则编译器拒绝为其子类生成默认拷贝运算符
注意:引用只能定义一次,定义后不能改变它的指向
06:若不想使用编译器自动生成的函数,就该明确拒绝
书上给出的方法是定义一个基类,该基类的拷贝构造和拷贝赋值为private,另你的类继承于该基类,则编译器无法合成默认拷贝构造和拷贝赋值函数。
但是在C++11中,只需要这么在函数后加上delete
class Empty{
public:
Empty(){};
Empty(const Empty&) = delete;
Empty& operator=(const Empty&) = delete;
~Empty();
};
07:为多态基类声明virtual析构函数
多态基类是为了通过基类指针来控制派生类对象
- polymorphic(多态)基类应声明一个virtual析构函数, 任何一个类带有任何virtual函数,它就应该用于一个virtual析构函数
带有虚构函数的类一般作为基类使用,而如果该类没有虚析构函数,当其派生类对象执行析构时,会导致局部销毁,进而导致内存泄漏
class Base
{
public:
Base() = default;
int testBase;
};
class Derived : public Base
{
public:
int testDerived;
};
int main()
{
Base* A = new Base;
Derived* B = new Derived;
A = B;
delete A; //执行此语句时只会删掉Base部分的成员,而Derived的部分不会被删除
}
注意即使基类指针指向了派生类对象,该指针也只能操作基类成员,因此要定义虚函数来操作派生类对象。
- 类的设计如不不是为了作为基类使用,或不是为了多态性,则不应声明virtual析构函数
因为会使对象占用内存增加。同时,为了让你的非基类、非多态用途的类不被继承,可以使用final关键字来禁止其派生。
class NoDerived final
{
...
}
08:别让异常逃离虚构函数
- 析构函数觉得不能抛出异常,如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或接受程序。
- 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应提供一个普通函数(而非在析构函数中)执行该操作
对于异常处理不熟悉,先记录,以后有机会补充
09:绝不在构造和析构过程中调用virtual函数
因为这类调用不会下降至派生类
考虑:
class Transaction{
public:
Transaction();
virtual void logTransaction() const = 0;
};
Transaction::Transaction()
{
logTransaction();
}
class BuyTransaction : public Transaction{
public:
virtual void logTransaction() const;
};
BuyTransaction a;
在定义a时,会先构造基类Transaction,此时调用的将会是基类的logTransaction(),因为在派生类对象的基类构造期间对象类型为基类而非派生类。
因此构造函数和析构函数都不能调用virtual函数
一种解决方案:
class Transaction
{
public:
explicit Transaction(const std::string &logInfo);
void logTransaction(const std::string &logInfo) const; //非虚函数
};
Transaction::Transaction(const std::string &logInfo)
{
//...
logTransaction(logInfo);
}
class BuyTransaction : public Transaction
{
public:
BuyTransaction(parameters) : Transaction(createLogString(parameters)) {}
private:
static std::string createLogString(parameters);
};
//令派生类将必要构造信息向上传给基类狗在函数
//从而让构造函数拜托虚函数
10:令operator=返回一个reference to *this
为了实现连锁赋值,即a = b = c = 15这种赋值,则需要这么做
class Widget{
public:
Widget& operator=(const Widget& rhs)
{
...
return *this;
}
};
此协议使用于所有赋值运算,+= , -= , ...
11:在operator=中处理"自我赋值"
考虑如下代码
class widget
{
public:
widget &operator=(const widget &rhs);
private:
widget *wp;
};
widget &widget::operator=(const widget &rhs)
{
delete wp;
wp = new widget(*rhs.wp);
return *this;
}
当自我赋值时,会造成未定义行为,因为rhs在被删掉后还在使用,有三种方法解决
使用“证同测试”来避免这个问题
widget &widget::operator=(const widget &rhs)
{
if (this == &rhs)
{
return *this;
}
delete wp;
wp = new widget(*rhs.wp);
return *this;
}
精心周到的语句排序
widget &widget::operator=(const widget &rhs)
{
widget* old = wp;
wp = new widget(*rhs.wp);
delete old;
return *this;
}
copy and swap技术
class widget
{
public:
widget &operator=(const widget &rhs);
void swap(widget& rhs); //交换*this和rhs的数据
private:
widget *wp;
};
widget &widget::operator=(const widget &rhs)
{
widget temp(rhs);
swap(temp);
return *this;
}
或使用值传递的方式,这里会产生副本,并让*this与副本的数据互换
widget& operator=(widget rhs)
{
swap(rhs);
return *this;
}
12:复制对象时勿忘其每一个成分
考虑如下代码
class Base
{
private:
int base;
};
class Derived : public Base
{
public:
Derived(const Derived &rhs);
Derived &operator=(const Derived &rhs);
private:
int derived;
};
Derived::Derived(const Derived &rhs) : derived(rhs.derived)
{
//do something
}
Derived& Derived::operator=(const Derived& rhs)
{
//do something
derived = rhs.derived;
return *this;
}
它们并未对Base部分进行初始化,会调用默认构造函数,而这往往造成不想要的结果。
考虑到Base部分往往是private的,可以使用相应base部分的拷贝构造函数
Derived::Derived(const Derived &rhs) : Base(rhs), derived(rhs.derived)
{
//do something
}
Derived& Derived::operator=(const Derived& rhs)
{
//do something
Base::operator=(rhs);
derived = rhs.derived;
return *this;
}
另外,不要让拷贝构造函数和拷贝赋值函数相互调用,如果其中重复代码较多,应放入第三个函数,由两者共同调用

浙公网安备 33010602011771号