首页 > 代码库 > C++对象复制控制

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 construstormove operator。(可移动是指一个成员要么是内置类型,要么提供了可访问的move construstormove 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

C++对象复制控制