effective C++读书笔记2

目录

了解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函数共同调用。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值