Effect C++学习笔记二:构造 析构 赋值运算

五:Know what functions C++ silently writes and calls了解C++默默编写并调用哪些函数

class Empty{};

class Empty
{
public:
	Empty() {}
	Empty(const Empty* rhs) {}
	~Empty() {}

	Empty& operator=(const Empty& rhs) {}
};

空类会被填充 一个copy构造函数,一个copy assignment操作符和一个析构函数,一个default构造函数,这些函数都是public inline。

唯有当这些函数被调用,它们才会被编译器创建出来。

注:编译器产出的析构函数是non-virtual,除非这个class的基函数自身声明有virtual析构函数。

如果打算在一个内含refrence成员的class内支持赋值操作,必须自己定义copyassignment操作符。

const同理,如果某个base class将copy assignment操作符声明为private,编译器也不会在派生类生成copy assignment操作符。

六:Explicitly disallow the use if compiler-generated functions you do not want.

若不想使用编译器自动生成的函数,就该明确拒绝。

可以将copy构造函数或者copy assignment操作符声明为private,阻止调用。

并且只声明不定义,防止友元或成员函数调用(产生链接错误)

class Uncopyable
{
protected:
	Uncopyable() {}
	~Uncopyable() {}
private:
	Uncopyable(const Uncopyable&);
	Uncopyable& operator=(const Uncopyable&);
};

继承该类 可以阻止子类对象被拷贝。

七:Declare destructions virtual in polymorphic base classes.为多态基类声明virtual析构函数

派生类对象有一个基类指针删除,如果该基类是non-virtual析构函数,实际上对象的派生部分的成分不会被销毁(派生类的析构函数不会执行)。

解决:给基类一个virtual析构函数。任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数。

建议:只有当class内含至少一个virtual函数,才为它声明virtual析构函数。

注: 纯虚函数析构  

trick

可以为一个类声明 定义 纯虚析构函数 使其成为abstract class (在没有纯虚函数时);

注:

带有多态性质的基类应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。

Classes的设计目的如果不是作为基类使用,或不是为了具备多态性,就不应该声明virtual析构函数。

八:Prevent exceptions from leaving destructions. 别让异常逃离析构函数。

例子:

class DBConnection
{
public:
	static DBConnection create();

	void close();
};

class  DBConn
{
public:
	 //...
	~DBConn()
	{
		db.close();
	}

private:
	DBConnection db;
};

在析构函数里 如果调用close失败引发异常,DBConn析构函数会传播该异常,允许它离开这个析构函数,会导致问题出现。

解决:

1.如果close抛出异常就结束程序。通常调用abort完成:

DBConn:~DBConn()

{
    try{db.close();}

    catch(...)

     {
         只做运转记录,记下对close的调用失败;

         std::abort();

    }

}

2.吞下调用close而发生的异常

不调用 abort()

一个新的策略:

class DBConn
{
public:
	void close()
	{
		db.close();
		closed = true;
	}
	~DBConn
	{
		if (!closed)
		{
			try
			{
				db.close();
			}
			catch (...)
			{
				//
			}
		}
	}
private:
	DBConnection db;
	bool closed;
};

总结:

析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。

如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

九:Never call virtual functions during construction or destruction   绝不在构造和析构过程中调用virtual函数

在base class构造期间,virtual函数不会下降到derived classes阶层。由于base class构造函数的执行更早于derived class构造,

base class构造时derived class的成员变量尚未初始化,编译器不会支持使用对象内部尚未初始化的部分。

析构函数同理,进入base class析构函数后对象就成为一个base class对象。

解决方案:

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);
};

可以在构造期间令derived classed将必要的构造信息向上传递至base class构造函数(注意父类的构造函数改为非virtual 并且调用函数改为static)

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

十:Have assignment operators return a reference to *this 令operator= 返回一个 reference to *this

为了实现“连锁赋值” 赋值操作符必须返回一个reference指向操作符的左侧实参。

例如:

class Widget
{
public:

	Widget& operator=(const Widget& rhs)
	{
		return *this;
	}
};

注:

令赋值操作符返回一个refrence to *this

十一:Handle assignment to self in operator=。  在 operator= 中处理 "自我赋值"

自我赋值再抹些情况下可能会引起空指针问题。

Widget& Widget::operator=(const Widget& rhs)
{
	if (this == &rhs) return *this; //证同测试

	//something
}

另外一种思路是不去管自我赋值安全,将目标放在解决自我赋值有可能出现的问题上面,(容错,拷贝对象等等)。

注:

1.确保对象自我赋值时operator= 有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址,精心周到的语句顺序,以及copy-and-swap

2.确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

十二:Copy all parts or an object. 复制对象时勿忘其每一个成分。

在为派生类写copy函数时需要考虑到基类的成员。

即编写一个copying函数确保复制所有local成员变量,调用所有base classed内适当的copying函数。

总结:

1.copying函数应该确保复制“对象内所有成员变量”及"所有base class成分"

2.不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。

发布了35 篇原创文章 · 获赞 5 · 访问量 414

猜你喜欢

转载自blog.csdn.net/qq_33776188/article/details/103049804