C++三五法则,看看你能不能理解

简介:三五法则规定了什么时候需要 1、拷贝构造函数  2、拷贝赋值函数  3、析构函数

1、需要析构函数的类也需要拷贝构造函数和拷贝赋值函数。

通常,若一个类需要析构函数,则代表其合成的析构函数不足以释放类所拥有的资源,其中最典型的就是指针成员(析构时需要手动去释放指针指向的内存)。

所以,若存在自定义(且正确)的析构函数,但使用合成的拷贝构造函数,那么拷贝过去的也只是指针,此时两个对象的指针变量同时指向同一块内存,指向同一块内存的后果很有可能是在两个对象中的析构函数中先后被释放两次。所以需要额外的拷贝控制函数去控制相应资源的拷贝。

所以这类例子的共同点就是:一个对象拥有额外的资源(指针指向的内存),但另一个对象使用合成的拷贝构造函数也同时拥有这块资源。当一方对象被销毁后,析构函数释放了资源,这时另一个对象便失去了这块资源(但程序员还不知道)。

classperson

{

public:

std::string*name;

intage;

person(constchar* the_name,intthe_age)

{

name =newstd::string(the_name);

age = the_age;

}

~person()

{

deletename;

}

};

intmain(void)

{

persona("me",20);

personb(a);

std::cout<< *b.name <

return0;

}

在上面的代码中对象b使用合成的拷贝构造函数拷贝对象a的值,这个程序没有什么实际意义。 

在main函数返回时,a,b变量会分别被析构,它们的成员name指向同一块内存,所以在程序结束时便会发生错误。

2、需要拷贝操作的类也需要赋值操作,反之亦然。

需要拷贝操作代表这个类在拷贝时需要进行一些额外的操作。  赋值操作   <<< = >>>    先析构+拷贝,所以拷贝需要的赋值也需要。反之亦然。

3、析构函数不能是删除的

如果类的析构函数是删除的,那么成员便无法销毁。所以在程序中不能定义这个类的对象。可以动态分配该对象并获得其指针,但无法销毁这个动态分配的对象(delete 失效)。

若上面的类的定义是

classperson

{

public:

std::string*name;

intage;

person(constchar* the_name,intthe_age)

{

name =newstd::string(the_name);

age = the_age;

}

private:

~person()

{

deletename;

}

};

则在main函数中定义变量a,b就会发生编译错误,然而,这样的定义却可以通过编译 

person *p;

p = new person("me", 20)

但是,这样动态分配的变量是不能被释放的,在调用 delete p 会发生编译错误, 内存泄露就这样发生了。

如果类的某个成员的析构函数是删除的或不可访问的(例 private的)则类的合成析构函数被定义为删除的  (小析构NO    大析构NO)

如果类的某个成员的拷贝构造函数是删除的或不可访问的(例 private的)则类的合成拷贝函数被定义为删除的 (小拷贝NO    大拷贝NO)

如果类的某个成员的析构函数是删除的或不可访问的(例 private的)则类的合成拷贝函数被定义为删除的 (小析构NO    大拷贝NO)

如果类的某个成员的析构函数是删除的或不可访问的(例 private的)则类的合成拷贝函数被定义为删除的  

4、如果一个类成员有删除的或不可访问的析构函数,那么其默认和拷贝构造函数会被定义为删除的。

如果没有这条规则,可能会创造出无法被删除的对象。 理论上来说,当析构函数不能被访问时,任何静态定义的对象都不能通过编译器的编译,所以这种情况只会出现在与动态分配有关的拷贝/默认构造函数身上。

5、如果一个类有const或引用成员,则不能使用合成的拷贝赋值操作。(无法默认构造的const成员的类 则该类就无默认构造函数)

原因很简单,const或引用成员只能在初始化时被赋值一次,而合成的拷贝赋值操作会对所有成员都进行赋值。显然,它不能赋值const和引用成员,所以合成的拷贝构造函数不能被使用,即会被定义为删除的。

本质上,当不可能拷贝、赋值、或销毁类的所有成员时,类的合成拷贝控制函数就被定义成删除的了。

!!!

以下这些内容 来自  c++ primer  第五版一书    归纳而来   详细内容可以 看书      值和指针类型的类设计等  

拷贝构造函数

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。

拷贝构造函数第一个参数必须是一个引用类型。此参数几乎总是一个const的引用。拷贝构造函数在几种情况下都会被隐式地使用。因此,拷贝构造函数通常不应该是explicit的。

合成拷贝构造函数

与合成默认构造函数不同,即使我们定义了其他构造函数,编译器也会为我们合成一个拷贝构造函数。

对某些类来说,合成拷贝构造函数用来阻止我们拷贝该类类型的对象。而一般情况,合成的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中。每个成员的类型决定了它如何拷贝。

拷贝初始化

直接初始化和拷贝初始化的差异。

string dots(10,',');    //直接初始化

string s(dots);         //直接初始化

string s2 = dots;       //拷贝初始化

当使用直接初始化时,我们实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。当我们使用拷贝初始化时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。

拷贝初始化通常使用拷贝构造函数来完成。拷贝初始化是依靠拷贝构造函数或移动构造函数来完成的。

拷贝初始化不仅在我们用=定义变量时会发生,在下列情况下也会发生

将一个对象作为实参传递给一个非引用类型的形参。

从一个返回类型为非引用类型的函数返回一个对象。

用花括号列表初始化一个数组中的元素或一个聚合类中的成员。

参数和返回值

拷贝构造函数被用来初始化非引用类类型参数,这一特性解释了为什么拷贝构造函数自己的参数必须是引用类型。如果其参数不是引用类型,则调用永远也不会成功——为了调用拷贝构造函数,我们必须拷贝它的实参,但为了拷贝实参,我们又必须调用拷贝构造函数,如此无限循环。

拷贝初始化的限制

vector<int> v1(10);     //直接初始化

vector<int> v1 = 10;    //错误:接受大小参数的构造函数是explicit的

如果我们希望使用一个explicit构造函数,就必须显式的使用:

void f(vector<int>);    //f的参数进行拷贝初始化

f(10);                  //错误:不能用一个explicit的构造函数拷贝一个实参

(vector<int>(10));     //正确:从一个int直接构造一个临时vector

如果我们希望使用一个explicit构造函数,就必须显式的使用:

编译器可以绕过拷贝构造函数

编译器被允许将下面的代码string null_book = "9-999-99999-9";

给写成string null_book("9-999-99999-9");//编译器略过了拷贝构造函数。

拷贝赋值运算符

类通过拷贝赋值运算符控制其对象如何赋值。

重载赋值运算符

重载赋值运算符本质上是函数,其名字由operator关键字后接表示要定义的运算符的符号组成。因此,赋值运算符就是一个名为operator=的函数。类似于任何其他函数,运算符函数也有一个返回类型和一个参数列表。

值得注意的是,标准库通常要求保存在容器中的类型要具有赋值运算符,且其返回值是左侧运算对象的引用。

合成拷贝赋值运算符

与处理拷贝构造函数一样,如果一个类未定义自己的拷贝赋值运算符,编译器会为他它生成一个合成拷贝赋值运算符。类似拷贝构造函数,对于某些类,合成拷贝构造运算符用来禁止该类型对象的赋值。

析构函数

析构函数释放对象使用的资源,并销毁对象的非static数据成员。

析构函数是类的一个成员函数,名字由波浪号接类名构成。它没有返回值,也不接受参数。

由于析构函数不接受参数,因此它不能被重载,对一个给定类,只会有唯一一个析构函数。

析构函数完成什么工作

如同构造函数有一个初始化部分和一个函数体,析构函数也有一个函数体和一个析构部分。在一个析构函数中,首先执行函数体,然后销毁成员。成员按初始化顺序的逆序销毁。

在对象最后一次使用之后,析构函数的函数体可执行类设计者希望执行的任何收尾工作。通常,析构函数释放对象在生存期分配的所有资源。

在一个析构函数中,不存在类似构造函数中初始化列表的东西来控制成员如何销毁,析构部分是隐式的。成员销毁时发生什么完全依赖于成员类型。销毁类类型的成员需要执行成员自己的析构函数。内置类型没有析构函数,因此销毁内置类型成员什么也不需要做。

隐式的销毁一个内置指针类型的成员不会delete它所指向的对象。

什么时候会调用析构函数

无论何时一个对象被销毁,就会自动调用其析构函数:

变量在离开其作用域时被销毁。

当一个对象被销毁时,其成员被销毁。

容器(无论是标准库容器还是数组)被销毁时,其元素被销毁。

对于动态分配的对象,当对指向它的指针应用delete运算符时被销毁。

对于临时对象,当创建它的完整表达式结束时被销毁。

由于析构函数自动运行,我们的程序可以按需要分配资源,而(通常)无须担心何时释放这些资源。

当指向一个对象的引用或指针离开作用域时,析构函数不会执行。

合成析构函数

下面的代码片段等价于Sales_data的合成析构函数:

classSales_data{

public:

~Sales_data(){}

};

在(空)析构函数体执行完毕后,成员会被自动销毁。认识到析构函数体自身并不直接销毁成员是非常重要的。

(在学习C/C++或者想要学习C/C++可以加我们的学习交流QQ群:712263501群内有相关学习资料)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容