C++对象复制控制

C++中对象的复制通过一系列的构造函数或者赋值函数来实现的,包括copy construstor,move construstor,copy assignment operator,move assignment operator,实现过程中还可能有destructor的参与,一般情况下,这些函数编译器会为我们自动合成,但还有很多时候,编译器会将他们合成为delete,因此需要我们自己编写。也有一些时候,我们需要禁止或者强制编译器为我们合成这些函数。

首先看一下什么情况下编译器不会为我们自动合成这些函数

这些函数都有一个共同的特性,即析构函数被标记为delete或者不可访问时,所有编译器自动合成的函数都会被标记为delete,因为编译器不自愿生成无法析构的对象。同时,如果他们的父类或者成员的对应函数被标记为delete或者不可访问时,编译器也不会自动合成,因为编译器自动合成的函数需要依赖这些函数实现(内置类型不遵循这些规则)

default construstor:

1)一个类提供了自定义的任何构造函数

<span style="font-size:18px;">class Demon
{
public:
    Demon(const string &s):id(s){}
private:
    string id;
};

 Demon d;   ///错误,无默认构造函数</span>

2)一个类拥有标记为delete或者无访问权限的析构函数的成员或父类,或者其本身析构函数被标记为delete或无权访问

class Demon
{
    ~Demon()=delete;
};

class DerivedDemon:public Demon
{

};

class WrapDemon
{
private:
    Demon d;
};

 ///下面三个对象创建都是错误的,因为
 Demon d;
 DerivedDemon dd;
 WrapDemon wd;

3)一个类拥有一个引用类型的成员,但并未对其进行类内初始化

<span style="font-size:18px;">class Demon
{
private:
    int &r;
};

Demon d;  ///错误,无默认构造函数</span>

4)一个类拥有一个无默认构造函数的父类,或者一个既无默认构造函数也未进行类内初始化的成员

<span style="font-size:18px;">class Mem
{
public:
    Mem(const string addr):address(addr){}
private:
    string address;
};

class Demon
{
private:
    Mem m;
};

class DerivedDemon:public Demon
{

};

Demon d;  ///错误,无默认构造函数
DerivedDemon dd;  ///错误,无默认构造函数</span>

5)一个类拥有一个既无显式的默认构造函数,也未进行类内初始化的常量成员成员(如int常量)

<span style="font-size:18px;">class Demonx
{
private:
    const int i;
};

class Demony
{
private:
    const string s;
};

Demonx dx;  ///错误,无默认构造函数
Demony dy;  ///ok</span>

copy construstor:

1)一个类拥有标记为delete或者无访问权限的析构函数的成员或父类

2)一个类拥有标记为delete或者无访问权限的复制构造函数的成员或父类

3)一个类显示定义了move construstor或者move operator

<span style="font-size:18px;">class Demonx
{
public:
    Demonx()=default;
    Demonx(Demonx &&dx)=default;
};

Demonx dxa;
Demonx dxb = dxa;  ///错误,copy construstor被标记为delete
Demonx dxc;
dxc = dxa;   ///错误,copy assignment operator被标记为delete</span>

copy assignment:

1)一个类拥有标记为delete或者无访问权限的复制赋值运算符的成员或父类

2)一个类拥有一个常量成员或者一个引用成员

3)一个类显式定义了move construstor或者move operator

destrustor:

一个类拥有标记为delete或者无访问权限的析构函数的父类或者成员,当一个类拥有标记为delete或private的析构函数时,它的子类其实是无法实现析构函数的,只能将其标记为delete,因为子类的析构函数会自动调用父类析构函数的。同样,当一个类拥有标记为delete或者不可访问的析构函数的成员时,它本身的析构函数也无法实现

<span style="font-size:18px;">class Demon
{
    ~Demon=delete;
};

class DerivedDemon:public Demon
{
public:
    ~DerivedDemon()
    {
        ///此析构函数会自动调用父类Demon的析构函数,
        ///因此错误
    };
};

DerivedDemon dd;   ///错误</span>

move construstor,move operator:

只有当一个类没有显式定义任何的复制构造函数,析构函数,复制赋值运算符,而且其所有成员和父类都可以移动,其析构函数可被访问,编译器词汇自动合成move construstor和move operator。(可移动是指一个成员要么是内置类型,要么提供了可访问的move construstor和move operator。常量成员和引用成员不可移动)

<span style="font-size:18px;">class Demonx
{
public:
    ~Demonx()=default;
};

class Demony
{
public:
    Demony()=default;
    Demony(const Demony&)=delete;
    Demony& operator=(const Demony&)=delete;
};

Demonx dax;
Demonx dbx(move(dax));  ///ok,会调用copy construstor
 ///当move construstor或者move assignment operator被标记为delete时,
 ///需要使用它们时,会调用对应的copy construstor或者 copy assignment

Demony day;
Demony dby(move(day));
///错误,copy construstor被标记为delete,但已经属于显式定义,因此
 ///move construstor也被编译器自动标记为delete,导致调用出错</span>

当我们不需要编译器自动合成的某些函数时,可以将其标记为delete,但我们自己将其标记为delete时,除了不能调用该函数以外,属于显式定义的范畴,上面的例子已经有所说明。

class Demon
{
    Demon(const Demon&)=delete;
};

Demon d;  ///错误,显式定义了一个构造函数,因此默认构造函数被标记为delete

当编译器将某个函数自动标记为delete,而我们有需要和编译器自动合成的函数一模一样的函数,可以将其标记为default以强制编译器合成,标记为default的函数虽然是编译器合成的,但也属于显式定义的范畴。而且,如果编译器无法合成该函数,例如为含有不可复制/移动的成员的类合成复制/移动构造函数,该函数会依然被标记为delete

class Demon
{
public:
    Demon(Demon &&)=default;
    Demon& operator=(const Demon &)=default;
    virtual ~Demon()=default;
};

Demon d;   ///错误,默认构造函数被标记为delete
class Demon
{
public:
    Demon()=default;
    Demon(Demon &&)=default;
    Demon& operator=(const Demon &)=default;
    virtual ~Demon()=default;
};

Demon dx;
Demon dy = dx;   ///错误,复制构造函数被标记为delete
class Demon
{
public:
    Demon(const Demon&)=delete;
    virtual ~Demon()=default;
};

class DerivedDemon:public Demon
{
public:
    DerivedDemon()=default;

};

DerivedDemon da;  ///错误,DerivedDemon依然被标记为delete

综上,一般情况下,一个类如果拥有不可访问或者标记为delete的析构函数的成员或父类,那么它本身也无法实现析构函数,因为析构函数会自动调用父类和成员的析构函数,当一个类拥有不可访问或者被标记为delete的其他复制控制函数的成员或父类时时,编译器无法合成这些函数,即使我们强制合成,因为编译器合成这些函数的办法只有一个,及调用他们父类或以及成员的对应函数,但是我们还可以自己编写这些函数,但是也会有相当的不便。

总后给一个例子,说明上面这些函数的调用规则

class Demon
{
public:
    Demon():id(++num)
    {
        cout<<"Demon "<<id<<" born"<<endl;
    }

    Demon(const Demon &d):id(++num)
    {
        cout<<"Demon "<<id<<" born by copy"<<endl;
    }

    Demon(Demon &&d):id(++num)
    {
        cout<<"Demon "<<id<<" born by move"<<endl;
    }

    Demon& operator=(const Demon &d)&
    {
        cout<<"Demon "<<id<<" value changed by copy"<<endl;
    }

    Demon& operator=(Demon &&)&
    {
        cout<<"Demon "<<id<<" value changed by move"<<endl;
    }

    virtual ~Demon()
    {
        cout<<"Demon "<<id<<" died"<<endl;
        --num;
    }
private:
    size_t id;
    static size_t num;
};

size_t Demon::num=0;

class DerivedDemon:public Demon
{
public:
    DerivedDemon():d_id(++d_num)
    {
        cout<<"DerivedDemon "<<d_id<<" born"<<endl;
    }

    DerivedDemon(const DerivedDemon& dd):Demon(dd),d_id(++d_num)
    {
        cout<<"DerivedDemon "<<d_id<<" born by copy"<<endl;
    }

    DerivedDemon(DerivedDemon &&dd):Demon(move(dd)),d_id(++d_num)
    {
        cout<<"DerivedDemon "<<d_id<<" born by move"<<endl;
    }

    DerivedDemon& operator=(const DerivedDemon &dd)&
    {
        Demon::operator=(dd);
        cout<<"DerivedDemon "<<d_id<<" value changed by copy"<<endl;
    }

    DerivedDemon& operator=(DerivedDemon &&dd)&
    {
        Demon::operator=(move(dd));
        cout<<"DerivedDemon "<<d_id<<" value changed by move"<<endl;
    }

    ~DerivedDemon()
    {
        cout<<"DerivedDemon "<<d_id<<" died"<<endl;
        --d_num;
    }
private:
    size_t d_id;
    static size_t d_num;
};

size_t DerivedDemon::d_num=0;
void test()
{
    Demon *dpx = new DerivedDemon();
    Demon *dpy = new DerivedDemon();

    *dpx = *dpy;
    *dpy = move(*dpx);

    delete dpx;
    delete dpy;

    DerivedDemon da;
    DerivedDemon db=da;
    DerivedDemon dc(move(da));
    DerivedDemon dd=move(db);
    da = db;
    dc = move(da);
}

运行结果:

Demon 1 born

DerivedDemon 1 born

Demon 2 born

DerivedDemon 2 born

Demon 1 value changed by copy

Demon 2 value changed by move

DerivedDemon 1 died

Demon 1 died

DerivedDemon 2 died

Demon 2 died

Demon 1 born

DerivedDemon 1 born

Demon 2 born by copy

DerivedDemon 2 born by copy

Demon 3 born by move

DerivedDemon 3 born by move

Demon 4 born by move

DerivedDemon 4 born by move

Demon 1 value changed by copy

DerivedDemon 1 value changed by copy

Demon 3 value changed by move

DerivedDemon 3 value changed by move

DerivedDemon 4 died

Demon 4 died

DerivedDemon 3 died

Demon 3 died

DerivedDemon 2 died

Demon 2 died

DerivedDemon 1 died

Demon 1 died

时间: 2024-08-29 19:50:03

C++对象复制控制的相关文章

C++独孤九剑第五式——人生几何(对象复制控制)

对酒当歌,人生几何? 譬如朝露,去日苦多. 人的一生可能惊涛骇浪,更可能波澜不惊,这次我们就来探讨一下"对象"(当然各位同学自己的对象不在本次讨论范围之内O(∩_∩)O,课后自己讨论吧)一生的"起起落落",即对象的复制控制. 复制控制包括三个部分:复制构造函数的调用.赋值操作符的调用.析构函数的调用.下面就这三个操作来逐一进行介绍,大家共同学习(*^-^*) 一.复制构造函数 复制构造函数:首先它也是构造函数,所以函数名与类名相同,没有返回值:其次,它只有一个形参,

第十九篇:复制控制( 下 ) --- 自定义析构函数

前言 经过前两篇随笔( 上  中 )的分析我们已经解决了具有指针成员的同类对象“ 干涉 ”问题.可惜,前面给出的解决方案代码还是不完整.还有什么问题呢?观察发现,构造函数里面有new的关键字出现,也就是说开辟了新的内存空间,我们也知道new必须也只能对应一个delete,而不应该让系统自己处理( 还不熟练new和delete用法的点这里 ),但这里和new对应的delete去哪里了? 解决思路 应该何时delete?显然,应该在对象销毁的时候delete.C++中定义了这样一种函数 --- 析构

对象复制、克隆、深度clone

-------------------------------------------------------------------------------- ------------------------------------------------- 知道Java对对象和基本的数据类型的处理是不一样的.和C语言一样,当把Java的基本数据类型(如int,char,double等)作为入口参数传给函数体的时候,传入的参数在函数体内部变成了局部变量,这个局部变量是输入参数的一个拷贝,所有的

稍微深入点理解C++复制控制【转】

通过一个实例稍微深入理解C++复制控制过程,参考资料<C++ primer>,介绍点基本知识: 1.在C++中类通过特殊的成员函数:复制构造函数.赋值操作符和析构函数来控制复制.赋值和撤销该类的对象时会发生什么. 2.复制构造函数(copy constructor)是一种特殊的构造函数,具有单个形参,该形参(常用const)是对该类类型的引用: 当定义一个新对象并用一个同类型的对象对它进行初始化时,将显示使用复制构造函数: 当将该类的对象传递给函数或从函数返回该类型的对象时,将隐式使用复制构造

C++拾遗(六)——复制控制

年前忙了几天,到现在才算是有空休息下来.先祝大家新年快乐,心想事成:)我也会发笑脸o.o 这篇博文主要介绍定义一个类型的对象时的复制控制方式,这部分内容之前有一定的了解但又浅尝辄止,始终感觉没能找到要点.年前又拿起书细细品读,算是有了一点新的了解.几天前就想动笔了,一直没时间,拖到现在. 每种类型定义了创建该类型的对象时会发生什么——构造函数定义了该类类型对象的初始化.类型还能控制复制.赋值或撤销该类型的对象时会发生什么——类通过特殊的成员函数:复制构造函数.赋值操作符和析构函数来控制这些行为.

C++之重载String ----- 构造函数、复制控制、重载操作符

本博文 我们通过 重新实现String类 来说明构造函数,复制控制,重载操作符. 一.构造函数(包括析构函数): 1:默认构造函数: 2:用户自己定义的构造函数 注意:当用户自己定义时,也要明确显示默认构造函数,这是因为,当我们没有定义自己的构造函数时,编译器会为我们自动合成一个,而我们定义了构造函数时,编译器默认构造函数改为我们自己定义的.这时就有可能出现错误: 3:析构函数: 具体声明与实现,代码如下: 1 声明部分: 2 String(); 3 String(const char*s);

C++复制控制

1.复制构造函数可用于: (1)根据另一个同类型的对象显示或隐式初始化一个对象 string str1="test";   //隐式 string str2=str1; //显示 str1为先调用string的字符串形参的构造函数,创建一个临时对象,然后,使用string复制构造函数将str1初始化为那个临时对象的副本. (2)复制一个对象,将它作为实参传给一个函数 (3)从函数返回时复制一个对象 当形参为非引用类型的时候,将复制实参的值.类似地,以非引用类型作返回值时,将返回retu

C++复制控制:拷贝构造函数

一.拷贝构造函数是一种特殊构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用.与默认构造函数一样 ,拷贝构造函数可由编译器隐式调用.拷贝构造函数应用的场合为: (1)根据另一个同类型的对象显式或隐式初始化一个对象. (2)复制一个对象将它作为实参传给一个函数. (3)从函数返回时复制一个对象. (4)初始化顺序容器中的元素. (5)根据元素初始化式列表初始化数组元素. 下面分别对以上5点进行说明. 1.对象的定义式. C++支持两种初始化形式:直接初始化和复制初始化.复制初始

c++OOP之复制控制 ------复制构造函数、赋值重载、析构

本博文我们讨论OOP复制控制的一些内容: 首先考虑对象复制的时机: 非引用类型 1):根据一个类去显式或者隐式初始化一个对象: 2):复制一个对象,将它作为实参传给一个函数: 3):从函数返回时复制一个对象.(string tolittle(string word)) 一个空类,编译器提供默认无参数构造函数.拷贝构造函数.赋值运算符以及析构函数,一共四个函数.(面试) 11.复制构造函数.赋值运算符以及析构函数,称为三法则,一旦提供了其中一个,务必提供其余两个.以String为例: a) 涉及到