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++》总结篇)构造/析构/赋值运算 -> 正文阅读

[C++知识库]《Effective C++》总结篇)构造/析构/赋值运算

条款五:了解C++默默编写并调用哪些函数

如果你自己没声明,编译器就会为它声明一个copy构造函数,一个copy assignment操作符,一个析构函数和一个default构造函数,并且都是public和inline的

#include <iostream>
using namespace std;
class  A
{
};

int main()
{
	A a;     //构造函数
	A a1(a);//copy 构造函数
	a1 = a;//copy assignment函数
		  //析构函数
	return 0;
}

注意以下几个细节:
1.编译器产生的析构函数是non-virtual的
2.编译器生成的copy构造函数和copy assignment操作符会以拷贝对象每一个成员来完成初始化。
3.C++不允许让引用改指向不同对象,所以如果你打算在一个内含引用成员的class内支持赋值操作,C++会拒绝编译那一行赋值操作。内含const成员也一样,因为更改const成员是不合法的。
4.如果某个base classes将copy assignment操作符声明为private,编译器将拒绝为其derived classes生成一个copy assignment操作符。

请记住:编译器可以暗自为class创建default构造函数,copy构造函数,copy assignment操作符以及析构函数。

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

所有编译器产出的函数都是public,如果你想阻止他们被创建出来,你可以将它们声明为private。藉由明确声明一个成员函数,阻止了编译器暗自创建其专属版本,而令这些函数为private,使得成功阻止客户调用它。

#include <iostream>
using namespace std;
class  UnCopyable
{
protected:
	UnCopyable() {}
	~UnCopyable() {} //允许derived对象构造和析构
private:
	UnCopyable(const UnCopyable& uc);
	UnCopyable& operator=(const UnCopyable& uc);
};
class  A:private UnCopyable
{
	 
};
int main()
{
	A a;
	A a1(a);//A::A(const A &)" (已隐式声明) --它是已删除的函数
	a1 = a;//A::operator=(const A &)" (已隐式声明) --它是已删除的函数
	return 0;
}

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

条款七:为多态基类声明virtual析构函数

看下面这段代码,
有一个基类BaseClass,和继承自BaseClass的A类和B类
getAorBClass函数:使用工厂模式创建A类和B类,返回BaseClass*
main函数调用getAorBClass函数生成a,b,然后delete a,b
观察结果

#include <iostream>
using namespace std;

class BaseClass
{
public:
	BaseClass() { cout << "BaseClass" << endl; }
	~BaseClass() { cout << "~BaseClass" << endl; }
};
class A:public BaseClass
{
public:
	A() { cout << "A" << endl; }
	~A() { cout << "~A" << endl; }
};
class B :public BaseClass
{
public:
	B() { cout << "B" << endl; }
	~B() { cout << "~B" << endl; }
};
BaseClass* getAorBClass(char className)
{
	BaseClass* baseclass=nullptr;
	if (className == 'A')
	{
		baseclass = new A(); //BaseClass/nA/n
	}
	else if(className=='B')
	{
		baseclass = new B();//BaseClass/nB/n
	}
	return baseclass;
}
int main()
{
	BaseClass* a = getAorBClass('A');
	BaseClass* b = getAorBClass('B');
	delete a;//~BaseClass
	delete b;//~BaseClass
	return 0;
}
/*
运行结果:
BaseClass
A
BaseClass
B
~BaseClass
~BaseClass
*/

我们发现,没有调用 ~ A,~B!!!!
因为C++明确指出,当一个derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义—实际执行时通常发生的对象是derived成分没被销毁。于是就会造成“局部销毁”的现象。

消除这个问题的做法很简单,给base class一个virtual析构哈桑农户,此后删除dervied class对象就会如你想要的那般,销毁整个对象。

#include <iostream>
using namespace std;

class BaseClass
{
public:
	BaseClass() { cout << "BaseClass" << endl; }
	virtual ~BaseClass() { cout << "~BaseClass" << endl; }
};
class A:public BaseClass
{
public:
	A() { cout << "A" << endl; }
	~A() { cout << "~A" << endl; }
};
class B :public BaseClass
{
public:
	B() { cout << "B" << endl; }
	~B() { cout << "~B" << endl; }
};
BaseClass* getAorBClass(char className)
{
	BaseClass* baseclass=nullptr;
	if (className == 'A')
	{
		baseclass = new A(); //BaseClass/nA/n
	}
	else if(className=='B')
	{
		baseclass = new B();//BaseClass/nB/n
	}
	return baseclass;
}
int main()
{
	BaseClass* a = getAorBClass('A');
	BaseClass* b = getAorBClass('B');
	delete a;//~A/n~BaseClass/n
	delete b;//~B/n~BaseClass/n
	return 0;
}
/*
运行结果:
BaseClass
A
BaseClass
B
~A
~BaseClass
~B
~BaseClass
*/

注意细节:
1.并不是所有情况都要使用virtual析构函数的,因为要实现virtual函数,对象必须携带某些信息。这样会使得对象体积增加。所以至少当class内含一个virtual函数才为它声明virtual析构函数。

2.当你需要一个抽象类,但是你手上没有任何pure virtual函数,那么你可以将析构函数变为pure virtual析构函数。

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

条款八:别让异常逃离析构函数

如果让析构函数吐出异常,总会带来“过早结束程序”或”发生不明确行为”的风险。
但如果你必须在析构函数执行一个动作而且这个动作可能会在失败时异常,那么你可以用一个Close函数代替这一个执行的动作,并且可以用一个Con来管理对象并调用对象身上的Close(),而且我们可以让客户自己调用Close(),这并不会给他们带来负担,而是给他们一个处理错误的机会,但如果他们不认为这个机会有用,也可以忽略他们,我们可以依赖自己析构函数去调用Close()。
代码如下:
假设A对象必须在析构函数处理某些可能发生异常的事情,那么我们给A对象声明一个close函数。
再用Con对象管理A对象,并给客户提供自行Close的机会,当然也可以不Close,而是等Con析构函数执行的时候自动调用A.Close()

#include <iostream>
using namespace std;

class  A
{
public:
	void close() {
		cout <<"close" << endl;
	}

private:

};

class Con
{
public:
	Con() :closed(false) {}
	void Close()//设下双重保险
	{
		a.close();
		closed = true;
	}
	~Con()
	{
		cout << "~Con " << closed << endl;
		if (!closed)
		{
			try
			{
				a.close();
			}
			catch (exception ex)
			{
				cout << ex.what() << endl;
			}
		}
	}
private:
	A a;
	bool closed;//设下双重保险
};
int main()
{
	Con c;
	c.Close();//用户主动调用Close
	Con c1;//用户不调用Close
	return 0;
}
/*
运行结果:
close
~Con 0
close
~Con 1
*/

请记住:
析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们或者结束程序。
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数执行该操作。

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

当你在构造函数内调用一个virtual函数的时候,由于derived class对象内的base class成分会在derived class自身成分被构造函数之前先构造妥当,所以此时调用virtual函数调用的会是基类的版本。
析构函数也是同理。

#include <iostream>
using namespace std;
class baseClass
{
public:
	baseClass()
	{
		func();
	}
	virtual void func() { cout << "func base" << endl; }
};
class  A:public baseClass
{
public:
	void func() { cout << "func A" << endl; }
private:

};

class B
{
public:
	void func() { cout << "func B" << endl; }
private:
	
};
int main()
{
	A a;
	a.func();
	B b;
	b.func();
	return 0;
}
/*
运行结果:
func base
func A
func B

*/

解决方案是,将func设置为non-virtual函数,并且要求基类传递一些必要的信息给baseClass构造函数。

#include <iostream>
using namespace std;
class baseClass
{
public:
	baseClass(const string& str)
	{
		func(str);
	}
	void func(const string& str) { cout << str << endl; }
};
class  A:public baseClass
{
public:	
	A():baseClass(createString()) { }
private:
	static string createString() {
		return "func A";
	}
};

int main()
{
	A a;
	return 0;
}
/*
运行结果:
func A
*/

你无法使用virtual函数从base classes向下调用,在构造期间,你可以藉由“令derived classes将必要的构造信息向上传递至base class构造函数”替换而加之弥补。

请记住:在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数那层)

条款十:令operator= 返回一个reference to *this

实现赋值连锁
如:
int x,y,z;
x=y=z=15;
这样连续赋值的实际原理是
x=(y=(z=15))
为了实现连锁赋值,赋值操作符必须返回一个引用指向操作符左侧实参。

#include <iostream>
using namespace std;

class  A
{
public:	
	A():x(0) {}
	A(int x):x(x){ }
	void print() { cout << x << endl; }
	A& operator=(const int& a)
	{
		x = a;
		return *this;
	}
	A& operator=(const A& a)
	{
		x = a.x;
		return *this;
	}
private:
	int x;
};

int main()
{
	A a(10);
	A a1, a2, a3;
	a2 = a3 = a1=20;
	a.print();//10
	a1.print();//20
	a2.print();//20
	a3.print();//20
	return 0;
}
/*
运行结果:
10
20
20
20
*/

不仅适用于赋值,还有+=,-=,*=运算。

请记住:令赋值(assignment)操作符返回一个reference to *this

条款十一:在operator=中处理”自我赋值”

比如A a;
a=a;
这看上去愚蠢但是合法。
因为上面的操作是合法的,所以我们在实现operator=代码时,很可能会写出不安全的版本

#include <iostream>
using namespace std;
class B
{

};
class  A
{
public:	
	A& operator=(const A& rhs)
	{
		delete b;
		b = new B(*rhs.b);
		return *this;
	}
	void print()
	{
		if (b)
			cout << b << endl;
		else
			cout << "b is null" << endl;
	}
private:
	B* b;
};

int main()
{
	A a;
	a.print();//CCCCCCCC
	a = a;
	a.print();//寄了没打印
	return 0;
}
/*
运行结果:
CCCCCCCC

*/

这里自我赋值的问题就是有可能this和 rhs可能是同一个对象。
这样的话,delete b同时也销毁了rhs的b。
有三种解法:
如果是自我赋值,就不做任何事。
为rhs数据制作一份副本,将
this数据和上述副本的数据交换。
rhs改为pass by value。这样rhs本身副本

//方法1
A& operator=(const A& rhs)
{
	if (&rhs == this)return *this;
	delete b;
	b = new B(*rhs.b);
	return *this;
}
//方法2
A& operator=(const A& rhs)
{
	B ori = *(rhs.b);
	B temp = ori;
	ori = *b;
	*b = temp;
	return *this;
}
//方法3
A& operator=(A rhs)
{
	B temp = *rhs.b;
	rhs.b = b;
	*b = temp;
	return *this;
}

请记住:
确保当对象自我赋值时operator= 有良好的行为。其中技术包括比较“来源对象”和”目标对象”的地址,精心周到的语句顺序,以及copy-and-swap
确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

条款十二:复制对象时勿忘其每一个成分
为derived class撰写copying函数必须小心的赋值base class成分。尤其是private部分,因为你无法访问它们,所以应该借由调用derived class的copying函数 相应的base class函数:

#include <iostream>
using namespace std;
class B
{
public:
	B(const string& str) :name(str) {}
	B(const B& rhs) :name(rhs.name){}
	B& operator=(const B& rhs) { name = rhs.name; return *this; }
	void print() { cout << name << endl; }
private:
	string name;
};
class  A:public B
{
public:	
	A(const string& str) :B(str) {}
	A& operator=(const A& rhs) { B::operator=(rhs); return *this; }
private:
};

int main()
{
	A a("123456");
	A a1(a);
	A a2("2");
	a2 = a1;
	a.print();//123456
	a1.print();//123456
	a2.print();//123456
	return 0;
}
/*
运行结果:
123456
123456
123456

*/

另外,这两个copy函数有近似相同的实现,所以你可能会想让某个函数调用另一个函数以避免代码重复,但是令拷贝操作符调用拷贝函数或者令拷贝函数调用拷贝操作符这是不合理的。
但是你可以建立 新的成员函数给两者调用,来消除代码重复。

请记住:
Copying函数应该确保复制“对象内的所有成员变量”以及”所有base class成分”。
不要尝试以某个copying函数实现另一个copying函数,。应该将共同机能放进第三个函数中,并由两个copying函数共同调用

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-18 17:26:01  更:2022-05-18 17:27:12 
 
开发: 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年11日历 -2024/11/23 18:50:50-

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