effective C++读书笔记2

news2024/9/27 23:27:05

目录

了解C++默认编写并调用的函数

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

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

不要让异常逃离析构函数

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

 在operator=处理自我赋值

复制对象时不要忘记其每一个成分


这是effective C++第二章节内容:构造/析构/赋值运算

了解C++默认编写并调用的函数

没有太多需要介绍的,编译器会默认创建构造,析构,copy构造,赋值运算(当你没有显示写时,而这些函数又要被调用时)

详细介绍:类与对象——中_"派派"的博客-CSDN博客_isleapyear头文件

这里补充几点:编译器产出的析构函数是个non-virtual,除非这个class 的 base class自身声明有virtual析构函数(这种情况下这个函数的虚属性; virtualness;主要来自base class)。

一般而言只有当生出的代码合法且有适当机会证明它有意义,编译器才会生成,万一两个条件有一个不符合,编译器会拒绝为class 生出operator= 。

例如:

class person
{
public:
	person(string& text, int val)
		:text_(text)
		,val_(val)
	{
	}
private:
	string& text_;
    int val_;
};
int main()
{
	string s1("hello");
	string s2("world");

	person p1(s1, 20);
	person p2(s2, 30);

	p1 = p2;  //报错
	return 0;
}

这里面对象中的成员是引用,引用只能只向一个数据,它也只能在初始化列表中进行初始化。C++并不允许“让reference改指向不同对象”。也就是说对象不被直接牵扯到赋值操作内。C++的响应是拒绝编译那一行赋值动作。除非自己定义定义copy assignment操作符。

例如:

   person& operator=(person& p)
	{
		text_ = p.text_;  //这也只是赋值操作,并不是改变引用的指向
		val_ = p.val_;
		return *this;
	}

补充:“内含const成员的classes,编译器的反应也一样。更改const成员是不合法的,所以编译器不生成赋值函数。最后还有一种情况:如果某个base classes 将 copyassignment操作符声明为private,编译器将拒绝为其derived classes生成一个copyassignment操作符,(赋值操作中,基类的成员是要调用基类的相关函数的,当基类显示写出且是不可见,编译器就不会自动生成)。

甚至基类不含成员,也会报错,类似于把基类构造函数设为私有,同样在派生类构造函数内报错,都是一些继承的语法,不再细说。。

例如:

请记住:

为驳回编译器自动(暗自〉提供的机能,可将相应的成员函数声明为private并且不予实现。使用继承base class 也是一种做法。
 

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

常见做法:将相关函数设为私有,只声明,不定义。用c++11的关键字,这里不细说了。

另外一种做法:例如不想对象被拷贝

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

直接看一段简单的代码:

class A
{
public:
	A()
	{
		p1 = new int[10];
	}
	~A()
	{
		delete[] p1;
	}
private:
	int* p1;
};
class B:public A
{
public:
	B()
	{
		p2 = new int[10];
	}
	~B()
	{
		delete[] p2;
	}
private:
	int* p2;
};

int main()
{
	B* b = new B;

	A* a = b;
    delete a;
	//......
	return 0;
}

会造成内存泄漏问题,正确的做法是将父类的析构函数声明为virtual。

virtual ~A()
{
	delete[] p1;
}

任何 class只要带有 virtual函数都几乎确定应该也有一个virtual析构函数。若class不含virtual 函数,通常表示它并不意图被用做一个base class。当class不企图被当作 base class,令其析构函数为virtual往往不太好。例如本书的例子:

 如果int占用32 bits,那么Point对象可塞入一个64-bit缓存器中。这样一个Point对象也可被当做一个“64-bit量”传给以其他语言如C或FORTRAN撰写的函数。然而当Point的析构函数是virtual,便不能了。当有virtual函数时,对象也不再和其他语言(如C)内的相同声明有着一样的结构(因为其他语言的对应物并没有 vptr),因此也就不再可能把它传递至(或接受自)其他语言所写的函数,除非你明确补偿vptr—一-那属于实现细节,也因此不再具有移植性。

当一个没有virtual函数时,有时候也会出现一些问题。

例如:

 一个类去继承string,但它的析构函数不是virtual。后面:

这就变成了上面的第一段代码的问题了。但你总不能去修改stl库的string吧。

相同的分析适用于任何不带virtual析构函数的 class,包括所有STL容器如vector, list, set,  unordered_map等等。如果你曾经企图继承一个标准容器或任何其他“带有non-virtual析构函数”的class,需要小心。

补充:有时候令class带一个pure virtual析构函数,可能颇为便利,由于抽象class总是企图被当作一个base class来用,而又由于base class应该有个virtua析构函数,并且由于pure virtual函数会导致抽象class,为你希望它成为抽象的那个class声明一个pure virtual析构函数,并进行定义(派生类会进行调用)。

关于多态的一些知识,可看这篇文章:C++多态_"派派"的博客-CSDN博客

请记住:

1.带多态性质的base classes应该声明一个virtual析构函数。如果class 带有任何virtual函数,它就应该拥有一个virtual析构函数。
2.Classes的设计目的如果不是作为base classes使用,就不该声明virtual析构函数。

不要让异常逃离析构函数

C++11并不禁止析构函数吐出异常,但它不鼓励你这样做。如果你的析构函数必须执行一个动作,而该动作可能会在失败时抛出异常,该怎么办? 假设你使用一个类负责数据库连接:例如:

 为确保客户不忘记在 DBConnection对象身上调用close(),一个合理的想法是创建一个用来管理DBConnection资源的class,并在其析构函数中调用close。

 这便允许客户写下这样的代码:

但如果该close调用导致异常,DBConn析构函数会传播该异常,也就是允许它离开这个析构函数。那会造成问题。

解决办法:

 一个较佳策略是重新设计DBConn接口,使其客户有机会对可能出现的问题作出反应。例如 DBConn自己可以提供一个close函数,因而赋予客户一个机会得以处理“因该操作而发生的异常”。

 请记住:

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

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

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

记住这个知识点:base class构造期间 virtual函数绝不会下降到derived classes阶层

看下面一段简单代码:

class person
{
public:
	person(int age=20)
		:age_(age)
	{	
		show1();
	}
	~person()
	{
		show2();
	}
	virtual void show1()
	{
		
		cout << "person age="<<age_ << endl;
	}
	virtual void show2()
	{
		cout << "delete person age=" << age_ << endl;
	}
private:
	int age_;
};

class student:public person
{
public:
	student(int age,int id)
		:age_(age)
		,id_(id)
	{
	}
	~student()
	{
		show2();
	}
	virtual void show1()
	{
		cout << "student age=" << age_<<endl;
	}
	virtual void show2()
	{
		cout << "delete student age=" <<age_<< endl;
	}
private:
	int age_;
	int id_;
};
int main()
{
	student* s = new student(18,2010100);
	delete s;
	return 0;
}

结果:

 当student对象在构造期间,会去调用person的构造函数,该构造函数有个virtual函数,但调用的是person内的版本,析构时也是一样的道理。一旦 derived class 析构函数开始执完后,对象内的derived class成员变量便呈现未定义值,所以C++视它们仿佛不再存在。进入 base class析构函数后对象就成为一个base class对象。

看本书的例子:

 

 BuyTransaction b;这句代码就是上面提到的问题,解决办法:

 请记住:

1.在构造和析构期间不要调用虚拟函数,因为这类调用从不下降至派生类(比起当前执行构造函数和析构函数的那层)。

 在operator=处理自我赋值

自我赋值:发生在对象赋值值给自己。

简单例子:

 

 上面的例子还不会出现问题。再往下看:若你使用一个class用来保存一个指针指向一块动态分配的位图:

例如:

 下面是operator=实现的代码:

 如果operator=函数内的*this(赋值的目的端)和rhs有可能是同一个对象。果真如此delete就不只是销毁当前对象的 bitmap,它也销毁rhs 的 bitmap。为防止这种错误,常见做法:

 但它不具备异常安全性,若new可能出现错误,pb可能是野指针等问题。第二种方案:

 在operator=函数内手工排列语句(确保代码不但“异常安全”而且“自我赋值安全”)的一个替代方案是,使用所谓的copy and swap技术。

 或者:

 将“copying动作”从函数本体内移至“函数参数构造阶段”却可令编译器有时生成更高效的代码。

请记住:

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

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

复制对象时不要忘记其每一个成分

直接看代码:

class Person
{
public:
	Person()
	{
		_age = 20;
		_name = "李华";
	}
	void Print()
	{
		cout << _name <<" "<<_age << endl;
	}
	int _age; // 年龄
	string _name; // 姓名
};

class Student : public Person
{
public:
	Student(int num=20123)
	{
		_stunum =num;
	}
	Student(Student& s)
	{
		_stunum = s._stunum;
	}
	int _stunum; // 学号
};
int main()
{
	Student s1;

	Student s2(2010100);
	s2._name = "张三";
	s2._age = 18;

	Student s3(s2);
	s1 = s2;

	cout << s1._name <<" " << s1._age <<" " << s1._stunum << endl;
	cout << s3._name << " " << s3._age << " " << s3._stunum << endl;
	return 0;
}

结果:

 上面的student 类继承了Person类的成员,但在copy构造函数时,并没有传实参给指定的base class构造函数,也就是说s3对象中的Person成员会被不带实参的Person构造函数初始化。(Person中的默认构造函数必须有一个,否则编译不通过),但上面的=是编译器默认生成的,会将s1成员值变为与s2成员值一样,不写copy 构造,让编译器默认生成,s3成员值也与s2成员值一样,可自行验证。

正确做法:

    Student(Student& s)
		:Person(s)
	{
		_stunum = s._stunum;
	}
	Student& operator=(Student& s)  //也在Person类中加个赋值=函数。
	{
		Person::operator=(s);
		_stunum = s._stunum;
		return *this;
	}

copy assignment函数与copy构造函数内有许多重复代码,但它们不应该互相调用。如果你发现你的 copy构造函数和 copy assignment操作符有相近的代码,消除重复代码的做法是,建立一个新的成员函数给两者调用。这样的函数往往是private而且常被命名为init。这个策略可以安全消除copy构造函数和copy assignment操作符之间的代码重复。

请记住:

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/129492.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java高效率复习-SpringMVC[SpringMVC]

SpringMVC的简介 表述层前端页面Servlet 入门案例-创建SpringMVC 创建Maven工程 创建Maven工程后&#xff0c;pom文件的打工方式是war包&#xff0c;表示web应用打包方式。 正确的web.xml文件创建路径 src\main\webapp\WEB-INF\web.xml添加依赖 spring-web:5.3.1是SpringM…

java 多线程编程

Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流&#xff0c;一个进程中可以并发多个线程&#xff0c;每条线程并行执行不同的任务。 多线程是多任务的一种特别的形式&#xff0c;但多线程使用了更小的资源开销。 这里定义和线程相关的另一个…

用户手册编写的终极指南

用户手册对于寻求了解产品和流程的用户来说是非常重要的。有时&#xff0c;它们甚至是一个公司向客户销售其产品的法律要求。 客户往往会在联系你的客户支持团队之前查阅你的用户手册&#xff0c;所以你的手册有可能为你节省支持成本。 在你的用户手册上投入大量时间和精力是…

PHP---表单传值

目录 一、表单传值的概念 二、表单传值的方式 &#xff08;1&#xff09;GET和POST的区别 &#xff08;2&#xff09;GET方式 &#xff08;3&#xff09;POST方式 三、接受表单数据 四、处理复选框的注意点 (1)单选框的数据处理 &#xff08;2&#xff09;复选框的数据…

机器学习100天(二十一):021 分类模型评价指标-ROC曲线和AUC

机器学习100天!今天讲的是:分类模型评价指标-ROC曲线和AUC 《机器学习100天》完整目录:目录 首先基于混淆矩阵,介绍两个新的概念:真正例率(TPR)和假正例率(FPR)。真正例率是预测为正且实际为正的样本的占所有正例样本的比例。TPR 越大,预测的正类中实际正类越多。计…

跳板攻击原理及如何追踪定位攻击者主机(下)

跳板攻击溯源中&#xff0c;我们需要先确定本地网络中是否存在攻击者的跳板。具体可参考&#xff08;跳板攻击原理及如何追踪定位攻击者主机&#xff08;上&#xff09;&#xff09; 那么在本地网络中发现跳板后&#xff0c;又要如何追踪定位攻击者主机&#xff1f; 这种情况…

【苹果相册推】增加家庭对方下载 zookeeper-3.4.12.tar.gz #编削配置装备摆设 vim conf/zoo.cfg

推荐内容IMESSGAE相关 作者推荐内容iMessage苹果推软件 *** 点击即可查看作者要求内容信息作者推荐内容1.家庭推内容 *** 点击即可查看作者要求内容信息作者推荐内容2.相册推 *** 点击即可查看作者要求内容信息作者推荐内容3.日历推 *** 点击即可查看作者要求内容信息作者推荐…

串口助手(简洁版)上位机软件零基础教程( C# + visual studio2019)

最近在开发一个项目&#xff0c;需要串口通信&#xff0c;所以学习怎么自己做一个串口通信助手 1.工程的建立 打开vs软件 -》 左上角 “文件”选项 -》 新建 -》 项目 &#xff0c;弹出对话框如下&#xff1a; 点击visual c# 选项卡 -》 1. 选择 Windows 桌面 -》 2.选择Windo…

互联网全面回暖?2023年主线任务:搞钱

2022年尾声将近&#xff0c;不知道大家最近有没有感受到一股「春风」&#xff0c;防疫政策不断放开&#xff0c;上下班地铁渐渐恢复了往日的繁忙和拥挤&#xff0c;经济正在加速复苏...... 作为职场人&#xff0c;大家最关心的问题就是&#xff0c;2023年就业形势是否会回暖&a…

CAN工具 - ValueCAN - 基础介绍

CAN/CANFD通讯广泛存在于整个车载网络中&#xff0c;几乎每一块软硬件的开发都需要用到CAN工具&#xff0c;除了我们所熟知的CANoe之外&#xff0c;今天我们来介绍一下另外一个CAN工具ValueCAN&#xff0c;这个工具个人认为应该在PCAN之上CANoe之下&#xff08;好用程度&#x…

Two Coupled Rejection Metrics Can Tell Adversarial Examples Apart

实现鲁棒性的一种补充方式是引入拒绝选项&#xff0c;允许模型不返回对不确定输入的预测&#xff0c;其中confidence是常用的确定性代理。 与此例程一起&#xff0c;我们发现置信度和校正置信度&#xff08;R-Con&#xff09;可以形成两个耦合的拒绝度量&#xff0c;这可以证明…

SQL Server2008数据库升级至SQL Server2012

文章目录引言升级步骤安装SQL Server 2008 R2 sp2清除处于非活动状态的SQL Server实例一些体会引言 今天接到了一个需求&#xff0c;服务器上的数据库需要从SQL Server2008升级到2012。根据之前的经验&#xff0c;感觉是一个非常有意思的过程&#xff08;事实上也是。这个过程…

离散数学—数理逻辑

数理逻辑部分数理逻辑命题逻辑逻辑连接词命题符号化命题公式命题公式的等价矛盾式与重言式※重言关系蕴含式的证明※重言关系等价式的证明析取范式与合取范式主析取范式主合取范式对于两种范式&#xff0c;我的一些看法命题逻辑推理直接推理间接推理谓词逻辑谓词演算的等价式和…

解决Ubuntu/Fedora/Arch有些位置无法切换输入法输入中文

在使用Linux的时候&#xff0c;往往会遇到有些位置无法输入中文的情况&#xff0c;这是怎么造成的呢&#xff1f; 类似下图这种位置&#xff0c;我想搜一下文件&#xff0c;但是发现无法切换输入法&#xff0c;不能输入中文&#xff0c;现在我们来解决一下。 首先安装gedit&…

86.分隔链表

给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 示例 1&#xff1a; 输入&#xff1a;head [1,4,3,2,5,2], x 3 输出&am…

数据结构(链表2)

链表图中的“^”符号表示所储存内容为空。 单循环链表 末结点的next不再指向空&#xff0c;而是指向头结点。 空单向循环链表&#xff0c;只有头结点。 优点&#xff1a;从表中任何一个结点出发&#xff0c;都可以顺next指针访问到所有结点。 不带头结点的单循环链表 为了循…

文件操作(五)—— 文件重定向(dup2)

1、什么是文件重定向&#xff1f; 简单来说&#xff0c;文件重定向可以理解为文件描述符重新选定自己的指向。 2号文件描述符和3号文件描述符原本的指向如下。2号文件描述符之所以指向了stderr&#xff0c;是因为数组下标为2的元素填充的是stderr结构体的地址 发生文件重定向…

Spring Cloud 配置中心多环境 bootstrap.yml

常见的配置类型&#xff1a; 服务配置&#xff1a;数据库配置、缓存配置、消息队列配置 开关配置&#xff1a;功能开发、业务开关、服务开关 业务配置&#xff1a;模块A、模块B bootstrap.yml 定义系统级别参数配置; 应用于&#xff1a; Spring Cloud Config 配置中心配置&…

SpringBoot2-5

先解决一个问题【报错1】java: 无效的目标发行版: 17 修改位置&#xff1a;maven的配置需要改为选取本地maven所在目录(这些都是java指定了1.8比较低但是又都用的版本导致) 再解决一个问题【报错2】 java: 无法访问org.springframework.boot.SpringApplication 错误的类文件…

【模型部署】人脸检测模型DBFace C++ ONNXRuntime推理部署(2)

系列文章目录 【模型部署】人脸检测模型DBFace C ONNXRuntime推理部署&#xff08;0&#xff09; 【模型部署】人脸检测模型DBFace C ONNXRuntime推理部署&#xff08;1&#xff09; 【模型部署】人脸检测模型DBFace C ONNXRuntime推理部署&#xff08;2&#xff09; 文章目录…