IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> Effective C++|Chapter 2:构造/析构/赋值运算 -> 正文阅读

[C++知识库]Effective C++|Chapter 2:构造/析构/赋值运算

Item05: 了解C++默默编写并调用哪些函数

  • 编译器会自动为空类empty class声明一个copy构造函数、一个copy assignment操作符和一个析构函数。此外,若无声明任何构造函数,编译器也会自动声明一个default构造函数;若已声明构造函数,则编译器不会再声明一个default构造函数。

    class Empty {};

  • 编译后相当于:

    class Empty {
    public:
    	Empty() {...}   // default构造函数
    	Empty(const Empty& rhs) {...}   // copy构造函数
    	~Empty() {...}  // 析构函数
    	Empty& operator=(const Empty& rhs) {...} // copy assignment操作符
    }
    

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

  • 上述已述,编译器会自动为class生成copy assignment和copy构造函数(如果程序员并未直接声明的话)。但是某些情况,并不需要编译器自动生成的函数。例如HomeForSale类不应有copy assignment和copy构造函数:
    HomeForSale h3(h1); // 不予编译
    HomeForSale h3 = h2; // 不予编译

  • 解决办法:为HomeForSale设计一个专门为了阻止copying动作而设计的base class内。【其实也可以在HomeForSale类的private函数声明copy assignment和copy构造函数,以阻止编译器自动生成,来阻止这两个操作】。

    class Uncopyable {
    protected:   // 允许dereived对象析构和构造
    	Uncopyable() {}  
    	~Uncopyable() {}  
    private: // 
    	Uncopyable(const Uncopyable&);
    }
    // 注意:是私有继承Uncopyable
    class HomeForSale: private Uncopyable {
    	...
    }
    

    任何对象,甚至member函数、friend函数尝试copy HomeForSale对象,编译器便试着生成一个copy构造函数和copy assignment操作符。这些函数的“编译器生成版”会尝试调用其base class的对应兄弟,那些调用会被编译器拒绝,因为其base class的拷贝函数是private。

    补充:public、protected、private继承的区别
    (1)public、protected、private访问标号的访问范围
    -public:可以被1)该类中的函数;2)子类的函数;3)友元函数;4)该类的对象访问;
    -protected:可以被1)该类中的函数;2)子类的函数;3)友元函数【包括设为友元的非成员函数、设为友元的其他类的成员函数、设为友元类的所有成员函数】访问;
    -private:可以被1)该类中的函数;2)其友元函数访问。
    (2)类的继承后方法属性变化
    -private属性不能够被继承。
    -使用private继承(默认为private继承),父类的所有属性在子类中变为private;
    -使用protected继承,父类的public和protected属性在子类中变为protected;
    -使用public继承,父类的所有属性不发生改变。

    • 请记住:
      • 为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。

Item07:为多态基类声明virtual析构函数

引用1:https://blog.csdn.net/qq_43118572/article/details/112553344
引用2:https://www.cnblogs.com/liushui-sky/p/5824919.html

尽管派生类中定义了析构函数来释放其申请的资源,但是并没有得到调用。原因是基类指针指向了派生类对象,而基类中的析构函数却是非virtual的,之前讲过,虚函数是动态绑定的基础。现在析构函数不是virtual的,因此不会发生动态绑定,而是静态绑定,指针的静态类型为基类指针,因此在delete时候只会调用基类的析构函数,而不会调用派生类的析构函数。这样,在派生类中申请的资源就不会得到释放,就会造成内存泄漏。

  • 请记住:
    • polymorphic(带多态性质的)基类应该声明一个virtual 析构函数。如果class 带有任何virtual函数,它就应该拥有一个virtual析构函数。
    • 反过来,如果Classes的设计目的不是作为base classes使用,或不是为了具备多态性,就不应该声明virtual析构函数。

Item08:别让异常逃离析构函数

为了安全,”析构函数尽可能的不要抛出异常“。如果非抛不可,语言也提供了方法,就是自己的异常,自己给吃掉。但是这种方法不提倡,我们提倡有错早点报出来(参考:https://www.cnblogs.com/zhyg6516/archive/2011/03/08/1977007.html)

  • 例如:如果在DBConn对象的析构函数调用close()函数:
    class DBConn {
    public:
    	...
    	~DBConn()
    	{
    		db.close();
    	}
    }
    
  • 由于这个close()函数可能抛出异常,因此要进行相应的处理:1)通过调用abort完成,或者2)什么也不做,吞下这个异常;
    【这两种方法都无法对“导致close抛出异常"的情况作出反应】
    DBConn::~DBConn()
    {
    	try { db.close(); }
    	catch (..) {
    		//制作运转记录,记下对close的调用失败
    		std::abort();  // 如果注释这行代码,就是吞下这个异常
    	}
    }
    
  • 如果要对“导致close抛出异常"的情况作出反应,则需要重新设计DBConn接口,使其客户有机会对可能出现的问题作出反应。
    【程序建议客户手动close()程序,如果客户没有手动close()程序,析构函数会帮他自动close()程序,但是出了问题,程序不负责解决】
class DBConn {
public:
	...
	void close()
	{
		db.close();
		closed = true;
	}
	~DBConn()
	{
		if (!closed) {
			try {
				db.close();
			}
			catch(...) {
			}
		}
	}
private:
	DBConnection db;
	bool closed;
}
  • 请记住:
    • 析构函数千万不要抛出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们或结束程序。
    • 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数执行该操作。

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

  • 在base class 构造/析构 期间,virtual函数不是virtual函数。【即在base class构造/析构期间,调用的virtual函数依然是base class的函数】

  • Example:

    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;
    	...
    }
    
    BuyTransaction b;
    
  • 当执行构造函数BuyTransaction b时,首先Transaction构造函数会被更早调用。Transaction构造函数的最后一行调用virtual函数logTransaction,而它调用的是Transaction【基类】的logTransaction版本,而不是BuyTransaction 【子类】 的版本。原因:子类BuyTransaction尚未初始化,所以面对它们,最安全的做法是视它们不存在。

  • 析构函数:一旦子类析构函数开始执行,对象内的子类成员变量便呈现未定义值。同理。


  • 解决办法:如何确保每次一有Transaction继承体系上的对象被创建,就会有适当版本的logTransaction被调用呢?一种做法是在class Transaction内将logTransaction函数改为non-virtual,然后要求子类构造函数传递必要信息给基类Transaction构造函数

    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(params) : Transaction(createLogString(params)) {...}
    	...
    private:
    	static std::string createLogString(params);
    }
    
  • Note:1)explicit的使用: google的c++规范中提到explicit的优点是可以避免不合时宜的类型变换,缺点无。所以google约定所有单参数的构造函数都必须是显示的。2)static的使用: 避免子类构造期间,意外指向“初期未成熟之BuyTransaction对象内尚未初始化的成员变量”。

Item10:令operator=返回一个reference to *this

  • 这是个共同遵守的协议,并无强制性。 【令赋值操作符返回一个reference to *this】
class Widget {
public:
	...
	Widget& operator=(const Widget& rhs)
	{
		...
		return *this;
	}
	Widget& operator+=(const Widget& rhs)
	{
		...
		return *this;
	}
}

Item11:在operator=中处理“自我赋值”

  • C++禁止对象“自我赋值”,但不禁止以by value方式传递一份副本

    // ①潜在的自我赋值
    a[i] = a[j];
    *px = *py;
    
    • 这里的不安全是若pb指向rhs.pb,那么delete pb会导致rhs.pb也被删除,而导致pb = new Bitmap(*rhs.pb)会报错。
    // ②一份不安全的operator= 实现版本
    class Bitmap { ... };
    class Widget {
    	...
    private:
    	Bitmap* pb;
    }
    
    Widget& Widget::operator=(const Widget& rhs)
    {
    	delete pb;
    	pb = new Bitmap(*rhs.pb);
    	return *this;
    }
    
    • 欲解决这种错误,在operator=前面进行“证同测试”,达到“自我赋值的检验目的”:【但它不具备异常安全性:如果"new Bitmap"导致异常,Widget最终会持有一个指针指向一块被删除的bitmap】
    Widget& Widget::operator=(const Widget& rhs)
    {
    	if (this == &rhs)  return *this;
    	delete pb;
    	pb = new Bitmap(*rhs.pb);
    	return *this;
    }
    
    • 让operator=具备“异常安全性”往往会自动获得“自我赋值安全”的回报。因此,一个更好的解决办法是,生成一个副本,然后赋值,最后删除这个副本:【现在即使new Bitmap产生异常,pb也不至于指向null】
    Widget& Widget::operator=(const Widget& rhs)
    {
    	//也可以加上这句证同测试语句,但这会使代码变大,
    	//并导入一个新的控制流分支,而两者都会降低执行速度。
    	if (this == &rhs)  return *this;  
    	Bitmap* pOrig = pb;
    	pb = new Bitmap(*rhs.pb);
    	delete pOrig;
    	return *this;
    }
    
    • 也可以使用copy and swap技术:【by value的传递方式避免了自我赋值】,但它牺牲了代码的清晰性。
    class Widget {
    ...
    void swap(Widget& rhs);  // 交换*this 和 rhs的数据
    }
    Widget& Widget::operator=(const Widget& this)
    {
    	Widget temp(rhs);
    	swap(temp);
    	return *this;
    }
    

Item12:复制对象时别忘其每一个成分

  • 最常见的错误就是:只复制了子类声明的成员变量,而忘记复制其继承父类的成员变量:
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)
{
	logCall("PriorityCustomer copy constructor");
}
PriorityCustomer::PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
	logCall("PriorityCustomer copy constructor");
	// 只复制了子类声明的成员变量,而忘记复制其继承父类的成员变量
	priority = rhs.priority;  
	return *this;
}
  • 任何时候只要你写子类的copying函数,必须很小心地复制其base class成分。那些成分往往是private,所以你无法直接访问它们,应该用子类的copying函数调用相应的base class函数

    // 子类的构造函数,调用base class的构造函数
    PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs) : Customer(rhs), priority(rhs.priority)
    {
    	logCall("PriorityCustomer copy constructor");
    }
    PriorityCustomer::PriorityCustomer::operator=(const   PriorityCustomer& rhs)
    {
    	logCall("PriorityCustomer copy constructor");
    	// 只复制了子类声明的成员变量,而忘记复制其继承父类的成员变量
    	Customer::operator=(rhs);  // 对base class成分进行赋值
    	priority = rhs.priority;  
    	return *this;
    }
    
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-28 00:06:47  更:2021-09-28 00:06:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/25 17:46:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码