- 5. 了解 C++ 默认编写并调用哪些函数
- 6. 若不想使用编译器自动生成的函数,就该明确拒绝
- 7. 为多态基类声明 virtual 析构函数
- 8. 别让异常逃离析构函数
- 9.绝不在构造和析构过程中调用 virtual 函数
- 10. 令 operator= 返回一个 reference to *this
- 11. 在operator= 中处理自我赋值
- 12. 复制对象时勿忘其每一个成分
5. 了解 C++ 默认编写并调用哪些函数
-
编译器可以暗自为 class 创建 default 构造函数、copy 构造函数、copy assignment 操作符, 以及析构函数。
//创建空类 class Empty { }; //空类等价于如下类 class Empty { public: Empty(){} //默认生成无参构造函数 ~Empty(){} //默认生成析构函数 Empty(const Empty& rhs){} //默认生成拷贝构造函数 Empty& operator=(const Empty& rhs){ return *this;} //默认生成赋值操作符 }; Empty e; //调用默认构造函数 Empty e1(e); //调用拷贝构造函数 Empty e1 = e; //调用拷贝构造函数 e2 = e1; // 调用赋值操作符 //1、若类中声明了有参构造函数, 则编译器不帮助生成默认构造函数, 不影响生成拷贝构造和赋值操作符。 template <typename T> class NamedObjected { public: NamedObjected(std::string& name, const T& value) : name_value(name), object_value(value) { } private: std::string& name_value; const T object_value; }; std::string s; NamedObjected<int> e(s, 1); //ok NamedObjected<int> e2(e); //ok e2 = e; //error //2、 如果类中含 const 或 引用成员变量,则编译器拒绝自动生成赋值操作符,因为违反了引用和 const 的不可变性。 //3、如果父类将赋值操作符声明为 private, 那么编译器也将拒绝为其派生类生成一个赋值操作符, 因为派生类的赋值操作符可以处理父类的赋值操作符(见条款12)。
6. 若不想使用编译器自动生成的函数,就该明确拒绝
-
为驳回编译自动提供的功能,可将相应的成员函数声明为 private 并且不予实现。使用像 Uncopyable 这样的 base class 也是一种做法。
class Uncopyable { protected: Uncopyable(){} ~Uncopyable(){} //不一定得是 virtual,因为此基类没有多态性质(见条款7) private: Uncopyable(const Uncopyable&); //阻止生成拷贝构造函数和赋值操作符 Uncopyable& operator=(const Uncopyable&); } //为什么继承 Uncopyable 会阻止子类自动生成 拷贝构造和赋值操作符 (见条款5和条款12) class HomeForSale : private Uncopyable //class 不再声明 拷贝构造和赋值操作符 { ... }
7. 为多态基类声明 virtual 析构函数
- polymorphic(带多态性质的) base class 应该声明一个 virtual 析构函数。 如果 class 带有任何 virtual函数, 他就应该拥有一个 virtual 析构函数。
- class 的设计目的如果不是作为 base class 使用, 或不是为了具备多态性, 就不该声明 virtual 析构函数。
-
当一个具有多态性质的派生类对象经由一个基类指针被删除时, 应该将基类析构函数加上 virtual,使得子类能析构。
//带有多态性质的 People 类 class People { public: People(){} ~People(){} virtual void Speak(){} }; class Student : public People { public: Student(){} Student(const char* name) { int len = strlen(name) + 1; m_name = (char*)malloc(len); strcopy(m_name, name); } virtual void Speak(){} ~Student(){ free(m_name); } private: char* m_name; }; People* p = new Student("s"); delete p; // 若 People 的析构函数未声明为 virtual, delete后,则不会调用 Student 的析构函数,从而导致 m_name 内存泄漏。 //声明为 virtual 就可解决 class People { public: People(){} virtual ~People(){} virtual void Speak(){} };
-
一个类不意图作为一个具有多态性质类时,无需将该类析构函数声明为 virtual
//如果一个类不包含 virtual 函数,那么这个类的析构函数无需声明为 virtual, 否则是个馊主意 //因为当其声明为 virtual 后, 类内会维护一个 vptr( virtual table pointer) 指针, 其指向一个由函数指针构成的数组, 称为 vtbl( vitual table). //这就导致该类体积会变大, 无意义的内存。
-
不要企图继承 STL 容器。
class SpecialString : public std::string { ... }; std::string* pss = new SpecialString("12"); delete pss; //同样的问题,可能导致 SpecialString 类内存泄漏
-
当带有多态性质的基类无需实例化时,使用纯虚函数声明, 并提供一份定义。
class AWOV { public: virtual ~AWOV() = 0; }; AWOV::~AWOV(){} //若不定义,编译不通过,因为派生类对象销毁时,会向上逐一调用父类析构函数, 发现没有定义,连接器就会发生抱怨。
8. 别让异常逃离析构函数
-
析构函数绝对不要吐出异常, 如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下他们(不传播)或结束程序。
-
如果客户需要对某个操作函数运行期间抛出的异常做出反应, 那么 class 应该提供一个普通函数(而非析构函数中)执行该操作。
class Widget { public: ~Widget(){} }; void doSomething() { std::vector<Widget> v; //若vector销毁到第二个Widget时,析构函数抛出异常将会导致剩余Widget未析构,可能导致资源泄漏。 } class DBConn { public: void close() //供客户使用的新函数,给客户一次捕获异常的机会。 { db.close(); closed = true; } ~DBConn() { if(!closed) //若客户未调用新函数close(),则利用析构函数调用 db.close(),并且捕获其异常, 记入日志。 { try { db.close(); } catch(...) { //记录日志 } } } private: DBConnection db; bool closed; };
9.绝不在构造和析构过程中调用 virtual 函数
- 在构造和析构期间不要调用 virtual 函数,因为调用从不下降至 derived class (比起当前执行构造函数和析构函数的那层)。
class Transaction
{
public:
Transaction()
{
init();
}
virtual void LogTransaction() const = 0; //日志记录
private:
void init()
{
//如果LogTransaction为纯虚函数,那么当父类钩爪
LogTransaction(); //调用 virtual 函数
}
};
class BuyTransaction : public Transaction
{
public:
virtual void LogTransaction() const
{
...
}
};
BuyTransaction b;
//问题1、当对象 b 构造时不会调用子类的 LogTransaction, 因为基类比子类先构造, 无法调用子类的成员函数。
//问题2、由于基类比子类先构造, 当基类 LogTransaction 为纯虚函数时,编译通过, 但运行时会报错。当为非纯虚函数时,会调用基类的 LogTransaction(), 也会和你的预期不一致
//令派生类将必要的构造信息向上传递至基类构造函数解决
class Transaction
{
public:
explicit Transaction(const std::string& logInfo)
{
logTransaction(logInfo);
}
void logTransaction(const std::string& logInfo) const; //变为非虚函数
};
class BuyTransaction : public Transaction
{
public:
explicit BuyTransaction( parameters) : Transaction(CreateLogString( parameters ))//通过子类参数将log信息传递给基类构造函数
{
}
void logTransaction(const std::string& logInfo) const; //变为非虚函数
private:
static std::string createLogString(parameters); //令此函数为 static, 避免函数中使用子类中未初始化的成员变量。(静态成员函数无法使用非 static 成员变量)
};
10. 令 operator= 返回一个 reference to *this
-
*令 operator= 返回一个 reference to this
//此协议并非强制性, 未遵循编译一样通过, 只是使得自定义类型与内置类型、标准程序库保持统一, 避免不必要的麻烦。 class Widget { public: Widget& operator=(const Widget& rhs) { return *this; } Widget& operator+=(const Widget& rhs) // 此协议也适用于 +=,-=,*=,等待 { return *this; } Widget& operator=(int rhs) //此操作符的参数类型也使用,即使不符合规定 { return *this; } };
11. 在operator= 中处理自我赋值
-
确保当对象自我赋值时 operator= 有良好行为。其中技术包括比较 “来源对象” 和 “目标对象” 的地址,精心周到的语句顺序、以及 copy-and-swap
-
确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
//1、一份不安全的 operator= 实现版本 class Widget { Widget() : data_(NULL), len_(0) { } Widget(const Widget& rhs){ ... } Widget& operator=(const Widget& rhs) { delete pb; //如果 rhs.pb 和 类内 pb 指向同一个对象,则此对象会 delete 两次 pb = new Bitmap(*rhs.pb); return *this; } ~MyString(){} private: Bitmap* pb; }; //此问题解决方法有三个。 //1、证同测试 Widget& operator=(const Widget& rhs) { //虽然行的通, 但是如果 new Bitmap 异常(不管是因为内存不足还是构造函数异常),Widget 最终会指向变成野指针。 if(*this == rhs) return *this; delete pb; pb = new Bitmap(*rhs.pb); return *this; } //2、改进,精心周到的语句 Widget& operator=(const Widget& rhs) { Bitmap* ptemp = pb; //记住原先的pb pb = new Bitmap(*ths.pb); //令pb指向 *pb 的一个副本(即使 new Bitmap 失败, pb仍保持原状。) delete ptemp; // 删除原先的pb return *this; } //3、方案2替代使用 copy-and-swap class Widget { void swap(Widget& rhs); //交换*this和rhs数据,详见条款29 }; Widget& operator=(const Widget& rhs) { Widget temp(rhs); //为 rhs 数据制作一份 swap(temp); return *this; }
12. 复制对象时勿忘其每一个成分
- copying 函数应该确保复制 ”对象内的所有成员变量“ 及 ”所有 base class 成分。“
- 不要尝试以某个 copying 函数实现另一个 copying 函数。 应该将共同机能放进第三个函数中,并由两个 coping 函数共同调用。
//copying 函数应该确保复制 ”对象内的所有成员变量“ 及 ”所有 base class 成分, 否则会导致某些成员变量未初始化,从而产生不预期的结果。
void logCall(const std::string& funcName);
class Customer
{
public:
Customer(){}
~Customer(){}
Customer(const Customer& rhs) : name_(rhs.name_)
{
logCall("Customer copy Constructor");
}
Customer& operator=(const Customer& rhs)
{
logCall("Customer copy assignment operator");
name_ = rhs.name_;
return *this;
}
private:
std::string name_;
};
// copying时,未初始化父类的成员变量
class PriorityCustomer : public Customer
{
public:
PriorityCustomer(const PriorityCustomer& rhs) : priority_(rhs.priority)
{
logCall("PriorityCustomer copy Constructor");
}
PriorityCustomer& operator=(const PriorityCustomer& rhs)
{
logCall("PriorityCustomer copy assignment operator");
priority_ = rhs.priority_;
return *this;
}
private:
int priority_;
}
// copying时,初始化父类的成员变量
class PriorityCustomer : public Customer
{
public:
PriorityCustomer(const PriorityCustomer& rhs) : Customer(rhs),priority_(rhs.priority)
{
logCall("PriorityCustomer copy Constructor");
}
PriorityCustomer& operator=(const PriorityCustomer& rhs)
{
logCall("PriorityCustomer copy assignment operator");
Customer::operator=(rhs); //对基类成分进行赋值动作
priority_ = rhs.priority_;
return *this;
}
private:
int priority_;
}