effective C++读书笔记(二)

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

2. 构造/析构/赋值运算(Constructors,Destructors,and Assignment Operators)

条款05: 了解C++ 默默编写并调用哪些函数(Knowwhat functions C++ silently writes and calls)

本条款讲了编译器自动为你创建了哪些函数。

用户定义一个empty class,当C++ 处理过它之后,如果你自己没声明,编译器就会为它声明一个copy 构造函数、一个copy assignment操作符和一个析构函数。此外,如果你没有声明任何构造函数,编译器也会为你声明一个default 构造函数。所有这些函数都是public inline。惟有当这些函数被需要(被调用),它们才会被编译器创建出来。即有需求,编译器才会创建它们。

1、默认构造函数:所有的非内置类型成员不初始化,所有类类型成员用其默认构造函数初始化

2、析构函数:默认是非虚的,除非这个类的基类自身声明有virtual析构函数。

3、拷贝构造函数:Empty(constEmpty & rhs) {...} 这是单纯将来源对象的每一个non-static成员变量拷贝到目标对象

4、拷贝赋值函数:与拷贝构造函数基本一致,只是类中的引用常量不能赋值(不然就违反了这两个类型的意义了)。如果这个类要调用基类的拷贝赋值函数,但是那个东西就是private,也编译器也不会给派生类生成copyassignment。编译器生成的拷贝赋值操作符:对于成员变量中有指针,引用,常量类型,我们都应考虑建立自己合适的拷贝赋值操作符。因为指向同块内存的指针是个潜在危险,引用不可改变,常量不可改变。

 

条款06: 若不想使用编译器自动生成的函数,就该明确拒绝(Explicitlydisallow the use of compiler-generated functions you do not want)

本条款告诉程序员,如果某些对象是独一无二的(比如房子),你应该禁用copy 构造函数或copy assignment 操作符,可选的方案有两种:

1copy构造函数和copy assignment操作符声明为private,并不予实现;

2方法一中,当member函数或friend函数调用copy构造函数(或copy assignment操作符),会有链接错误。用下面的方法可以将连接期间的错误移到编译期间。定义一个Uncopyable公共基类,让所有独一无二的对象继承它。

class Uncopyable {
protected: //允许derived对象构造和析构
 Uncopyable() {}
 -Uncopyable(){}
private:
 Uncopyable(const Uncopyable&}; //但阻止copying
 Uncopyable& operator=(const Uncopyable&);
};

class HomeForSale: private Uncopyable{// 注意这个地方不用public 继承了
 
};

一个实际应用场景:在含有指针成员的类的拷贝中:

class Array{
 int *data;
};

Array a;
Array b;
b = a;

编译器生成的缺省的构造拷贝函数和拷贝运算符的重载函数,对指针实行的是按位拷贝,仅仅只是拷贝指针的地址,而不会拷贝指针的内容。因此在执行完上面的代码之后,a.data和b.data指向的同一地址。当a或者b中任意一个结束其生命周期调用析构函数时,会删除data。由于他们的data指向的是同一个地方,两个实例的data都被删除了。但另外一个实例并不知道它的data已经被删除了,当企图再次用它的data的时候,程序就会不可避免地崩溃。解决方法除了上面的两条外,还可以重载copy构造函数和copy assignment操作符,让它们拷贝的不是地址,而是数据

 

条款07: 为多态基类声明virtual 析构函数(Declaredestructors virtual in polymorphic base classes)

由来:当derivedclass 对象经由一个baseclass 指针被删除,而baseclass的析构函数不是虚的,其结果未定义,通常是base被销毁了,derived的非base部分没有被销毁。我们用virtual的目的,是为了子类实现个性化,因此,虚的目的是确有其用的,不能瞎虚,那样会影响移植性和代码性能

本条款阐述了一个程序员易犯的可能导致内存泄漏的错误,总结了两个程序员应遵守的编程原则:

1 polymorphic (带多态性质的) base classes 应该声明一个virtual 析构函数。如果class 带有任何virtual 函数,它就应该拥有一个virtual 析构函数。这样,当用户delete基类指针时,会自动调用派生类的析构函数(而不是只调用基类的析构函数)。

2Classes 的设计目的如果不是作为base classes 使用,或不是为了具备多态性(polymorphically),就不该声明virtual 析构函数。这是因为,当用户将一个函数声明为virtual时,C++编译器会创建虚函数表(vtbl, virtualtable)以完成动态绑定功能,这将带来时间和空间上的花销。比如上面的 Uncopyable并不是为了多态。

3)书上一句话"如果你曾经企图继承一个标准容器或者任何其他带有non-virtual析构函数的class,拒绝诱惑"

(4)如果你想让一个类成为抽象类,但是手头有没有纯虚函数,那么用析构函数做纯虚函数是个好主意。不过要注意:你必须为这个purevirtual析构函数提供一份定义AWOV::~AWOV(){   };

 

条款08:别让异常逃离析构函数(Preventexceptions from leaving destructors)

两种情况下析构函数会被调用:一个对象的声明周期自然地结束;或者是前面抛了个异常,由于异常处理机制,导致对象被删除。我们在析构函数中无法判断是那种情况激活了析构函数,如果析构再抛一个,那么控制权交到了函数外,C++将调用terminate函数。

1析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。析构不能抛出异常,主要是因为,我们捕捉不到,那样析构抛出的异常肆意乱跑,不受控制,导致不明确行为或者程序终止。针对上述的问题,我们有的解决方法:析构函数吞下任何异常catch(...)

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

class DBConn{
public:
 ...
 void close()
 {
  db.close();
  closed = true;
 }
 ~DBConn()
 {
  if (!closed)     
  {
   try{       //如果客户没有关闭连接
    db.close();
   }
   catch(...){
    制作运转记录,记下对close的调用失败;//结束程序或吞下异常
   }
  }
 }
private:
 DBConnection db;
 bool closed;
};

 

条款09: 绝不在构造和析构过程中调用virtual 函数(Nevercall virtual functions during construction or destruction)

不要在构造和析构过程调用virtual函数,这类调用从不下降至当前执行构造函数(析构函数)的derived class(比起当前执行构造函数和析构函数的那层)。因为在derived class对象的base class构造期间,对象的类型是base class而不是derived class;同理,一旦derived class析构函数开始执行,对象内的derived class成员变量便呈现未定义值,C++视他们仿佛不再存在,进入base class析构函数后对象就成了base class对象。

 

条款10: operator= 返回一个referenceto *this(Have assignment operators return a reference to *this)

本条款告诉程序员一个默认的法则:为了实现连锁赋值,应令operator=operator +=operator -=)返回一个reference to*this

      intx, y, z;
      x = y = z = 15;
     
为了实现连锁赋值,赋值操作符必须返回一个引用指向操作符的左侧实参。
     
即:
        Widget & operator = (constWidget &rhs)
        {
            ...
          
 return *this;
        }
     
所有内置类型和标准程序库提供的类型如stringvectorcomplex或即将提供的类型共同遵守。

 

条款11: operator= 中处理自我赋值”(Handleassignment to self in operator=)

本条款给出了写copy assignment函数时应该注意的几点和相应的解决技术:比较来源对象目标对象的地址、精心周到的语句顺序、以及 copy-and-swap

1)为了防止自我赋值,在operator=最前面应该有一个证同测试(identity test)”

Widget& Widget::operator=(const Widget& rhs)
{
 if (this == &rhs) return *this; //“证同测试”,如果是自我赋值,直接返回
 
 delete pb;
 pb = new Bitmap(*rhs.pb);
 return *this;
}

2)上面的代码中,如果“new Bitmap”导致异常(分配内存不足或是Bitmapcopy构造函数抛出异常),Widget 最终会持有一个指针指向被删除的Bitmap。解决方法是:先复制副本,再删除。

Widget& Widget::operator=(const Widget& rhs){

 Bitmap* pOrig = pb;    //保存副本
 pb = new Bitmap(*rhs.pb);
 delete pOrig;
 return *this;
}

3)一个确保异常安全自我赋值安全的替代方案,使用copy-and-swap技术。

class Widget {
 ……
 void swap(Widget& rhs); //交换*this 和rhs 的数据:详见条款29
 ……
};

Widget& Widget::operator=(Widget &rhs) //rhs是被传对象的一份复件(副本),注意这里是pass by value.
{
 Widget temp(rhs);
 swap(rhs); //将*this 的数据和副本的数据互换
 return *this;
}

 

条款12: 复制对象时勿忘其每一个成分(Copycall parts of an object)

本条款阐释了复制对象时容易犯的一些错误,给出的警告是:

1Copying 函数应该确保复制对象内的所有成员变量所有base class 成分。复制所有local成员变量;调用所有base classes内的适当的copying函数。

 如果你在类中添加一个成员变量,你必须同时修改相应的copying函数(所有的构造函数,拷贝构造函数以及拷贝赋值操作符)

2不要尝试以某个copying 函数实现另一个copying 函数。应该将共同机能放进第三个函数中,并由两个coping 函数共同调用。具体地,如果copy 构造函数和copy assignment 操作符有相近的代码,copy assignment操作符和copy构造函数之间不要互相调用。消除重复代码的做法是,建立一个新的成员函数给两者调用。这样的函数往往是private 而且常被命名为init

条款07:为多态基类声明virtual析构函数 因为C++明确指出,当derived class对象经由一个base class指针被删除,而该base class带着一个non-vritual析构函数,其结果将未有定义---通常发生的是对象的derived成分没有被销毁。 解决:给base class一个virtual析构函数。此后删除derived classs对象就会如你想要的那样,销毁整个对象,包括所有derived class成分: class TimeKeeper { public: TimeKeeper(); virtual ~T 阅读详情

相关推荐

effective C++读书笔记

11. 1)       如果你为class添加一个成员变量(尤其是类成员变量),你必须同时修改copying函数,所有构造函数以及任何非标准形式的operator=。 2)       copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。在子类中要注意调用基类的构造函数(或copying构造,或operator=等)来完成对基类的操作。 3)

projectoflion123的专栏 360

Effective C++ | 读书笔记

第三版第三四章内容

m0_74795952的博客 1075

Effective C++读书笔记

Effective C++读书笔记 条款26:尽可能延后变量定义式的出现时间 尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率。 条款27:尽量少做转型动作 如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_casts。如果有个设计需要转型动作,试着发展无需转型的替代设计。 如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需要将转型放进他们自己的代码内。 尽量使用C+±style转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有

WINGREZ‘S BLOG 418

Effective C++读书笔记

19、避免返回handles(指针,引用,迭代器)指向对象内部成分 即使函数前面加上const,变成 const Point& upperLeft() const{} 意味着调用者拿到Point引用但是不能涂写,但是让调用者拿到了引用,通过拿到的引用对象调用里面的成员,如果执行这句话时该引用已经不存在(销毁),那么会变成一个空悬、虚吊的对象 20、了解inlining的里...

zhangxiaofan666的博客 242

Effective C++ 读书笔记

所谓编译单元是指产出单一目标文件的那些源码,包括源码文件和其所包含的头文件。static对象是指其寿命是从被构造出来到程序结束为止。该对象包括global对象、namespace内的对象、文件内、class内、函数内被声明为static的对象。函数内的static对象称为local static对象,其他均为non-local static对象。

qq_53660404的博客 765

Effective C++ 读书笔记

条款5:定义好一个空类时,编译器会自动生成无参构造函数、copy构造函数、copy assignment(拷贝赋值)操作符和析构函数 程序员自定义的函数的优先级高于编译器自动生成的,当程序员自己实现了上述函数时,编译器就不会生成默认的函数。但是,当编译器生成的默认函数无法满足你的功能时,就需要程序员自己去定义函数了。 条款6:当不想使用编译器生成的默认函数时,将这些不想使用的默认函数自己声明为private,并且只声明,不实现 如果仅仅声明为private的,且实现了(即使是空实现),类的friend

如沐清风的博客 179

Effective C++读书笔记

、构造/析构/赋值运算 条款05:了解C++默默编写并调用哪些函数 如果你自己没声明,编译器就会为它声明(编译器版本的)一个copy构造函数,一个copy assignment操作符和一个析构函数。此外如果你没有声明任何构造函数,编译器也会成为你声明一个默认构造函数。所有这些函数都是public且inline。 惟有当这些函数被需要(被调用),它们才会被编译器创建出来。 注意

u010358881的专栏 394

Effective C++读书笔记

构造/析构/赋值运算 几乎你写的每个class都会有一或多个构造函数、一个析构函数、一个拷贝赋值操作符。如果这些函数犯错,会导致深远且令人不愉快的后果,遍及整个classes。所以确保它们行为正确是生死攸关的大事。 条款05:了解C++默默编写并调用哪些函数 如果你自己没声明,编译器就会为类声明(编译器版本的)一个拷贝构造函数,一个拷贝赋值操作符和一个析构函数。此外如果你没有声明任何构造

小李的专栏 609

Effective C++ 条款总结 读书笔记

Item Effective C++ 条款 item_20 Avoid data members in the public interface This rule warns you if data members are present in the public interface. Instead of making data members public, you should

聚精会神搞建设 1448

effective c++读书笔记(

Constructors, Destructors, Assignment operators

VanityLost_CY的博客 327

Effective C++读书笔记()

条款5:了解C++默默编写并调用哪些函数 如果打算在一个类中内含引用成员的函数中赋值,必须自己定义一个copy assignment 操作符 如果某个base class将copy assignment 声明为private,则在derived中编译器拒绝为其生成copy assignment操作符,因为derived class 可以调用base class 的copy函数. 条款6:防止使用编...

人总是要有梦想的QAQ的博客 101

《More Effective C++读书笔记)Exception 异常

这事篇读书笔记,只记录自己的理解和总结,一般情况不对其举例子具体说明,因为那正是书本身做的事情,我的笔记作为梳理和复习之用,划重点。我推荐学C++的人都好好读一遍Effective C++ 系列,真是好书啊,对于学完C++ 基础知识的人,这是本高阶秘籍。 笔记 条款 9 - 15 关注的主题是异常。关注1、异常可能引起的资源泄露(强烈推荐使用智能指针)2、异常是如何抛出的和3、异常的成本。目的...

weixin_30896825的博客 123

effective C++ 读书笔记

章 构造 / 析构 / 赋值函数 条款05:了解C++默默编写并调用哪些函数 n        编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数 copy构造函数和copy assignment操作符,编译器创建的版本只是单纯地将来源对象的每一个non-static成员变量拷贝到目标对象。 条款06:若不想使用编

kindlucy的专栏 693

【转】effective C++读书笔记

<br />转自:http://hi.baidu.com/a024014/blog/item/617b690ac43e8b1a95ca6ba2.html<br /> <br />类和函数:设计与声明<br /><br /><br />条款18 :争取使类的接口完整并且最小<br /><br />条款19 :分清成员函数,非成员函数和友元函数<br />如果f 必须是虚函数,就让它成为C 的成员函数。<br />operator>>和operator<<决不能是成员函数。如果f 是operator>>或o

hq0520的专栏 346

《More Effective C++读书笔记-技术()

30、proxy classes(代理类)可以用两个类来实现维数组:Array1D是一个一维数组,而Array2D则是一个Array1D的一维数组。Array1D的实例扮演的是一个在概念上不存在的一维数组,它是一个代理类。代理类最神奇的功能是区分通过operator[]进行的是读操作还是写操作,它的思想是对于operator[]操作,返回的不是真正的对象,而是一个 proxy类,这个代理类记录了对

爱橙子的OK绷的专栏 640
上一篇: effective C++读书笔记(一)
下一篇: effective C++读书笔记(三)
necrazy
博客等级 码龄18年 27粉丝 28原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值