拷贝构造函数、拷贝赋值运算符和析构函数

当定义一个类时,我们显式或隐式的指定对象拷贝,移动,复制和销毁时做什么。通过5类特殊成员函数来控制这些操作:拷贝构造函数、拷贝复制运算符、移动构造函数、移动复制运算符和析构函数。如果一个类没有定义这些,编译器会自动定义缺失的操作。但对于一些类来说,默认定义可能导致灾难。

拷贝构造函数定义了当用同类型对象初始化另一个对象时做什么。

1.拷贝构造函数

拷贝构造函数的声明

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

class Foo
{
public:
    Foo();  //默认构造函数
    Foo(const Foo&); //拷贝构造函数
    // ...
}

虽然我们可以定义非const引用,但该参数几乎总是一个const引用。

为何参数是引用?:

如果不是引用,在调用拷贝构造函数时我们需要拷贝它的实参,但为了拷贝它的实参,我们又需要调用它的拷贝构造函数,调用永远不会成功。

编译器合成的拷贝构造函数

如果我们没有定义拷贝构造函数,编译器默认会合成一个。它会依次将给定对象的非静态成员变量逐个拷贝到正在创建的对象中。

内置成员变量直接拷贝,类对象成员使用其拷贝函数,数组则是逐个元素拷贝。

用拷贝的形式进行初始化

string dot(3,'.');  //直接初始化,dot="..."
string s(dot);  //直接初始化
string s1=dot; //拷贝初始化 使用=,将=右值拷贝来初始化左值,没使用=则为直接初始化
string s2="123" //拷贝初始化
string s3=string("123") //拷贝初始化

当使用关键字explicit,必须显性使用构造函数。

对于某些类型,这一情况非常理想。但在大部分情况中,隐式转换却容易导致错误(不是语法错误,编译器不会报错)。隐式转换总是在我们没有察觉的情况下悄悄发生,除非有心所为,隐式转换常常是我们所不希望发生的。通过将构造函数声明为explicit(显式)的方式可以抑制隐式转换。也就是说,explicit构造函数必须显式调用。

引用一下Bjarne Stroustrup的例子:

class String{

      explicit String(int n);

      String(const char *p);

};

String s1 = 'a'; //错误:不能做隐式char->String转换

String s2(10);   //可以:调用explicit String(int n);

String s3 = String(10);//可以:调用explicit String(int n);再调用默认的复制构造函数

String s4 = "Brian"; //可以:隐式转换调用String(const char *p);再调用默认的复制构造函数

String s5("Fawlty"); //可以:正常调用String(const char *p);

void f(String);

­

String g()

{

    f(10); //错误:不能做隐式int->String转换

    f("Arthur"); //可以:隐式转换,等价于f(String("Arthur"));

    return 10; //同上

}

在实际代码中的东西可不像这种故意造出的例子。

发生隐式转换,除非有心利用,隐式转换常常带来程序逻辑的错误,而且这种错误一旦发生是很难察觉的。

除了用=定义变量时会调用构造函数,以下情况也会发生:

  • 将对象作为实参传递给一个非引用的形参
  • 返回一个非引用对象
  • 花括号列表初始化一个数组元素

2.拷贝赋值运算符

例:Foo foo1,foo2;

        foo1=foo2;

重载运算符

重载运算符本质是函数,operate关键字加运算符号。

class Foo{
public:
Foo& operator=(const Foo&);    //赋值运算符
//...
}

3.析构函数

析构函数可以用来释放对象使用的资源,并销毁非static数据成员。默认的合成析构函数为空,执行空函数体后成员自动销毁。函数本身并不直接销毁成员。成员在析构函数体后隐含的析构阶段被销毁。整个对象销毁过程中,析构函数体是作为成员销毁步骤之外的另一部分进行的。

形式

波浪号+类名,如:~Foo();

析构函数不接受参数,不能被重载,一个类只有唯一析构函数。

调用时间

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

  • 变量离开作用域
  • 对象被销毁,其成员被销毁
  • 容器销毁,元素被销毁
  • 动态分配的对象,对其指针delete
  • 临时对象创建结束时被销毁

什么时候需要拷贝构造函数和析构函数

通常需要析构函数的类也需要拷贝构造函数。类成员存在动态内存分布时,使用合成的构造函数会使得多个对象指向相同的内存,使用默认的合成析构函数会delete多次,会导致错误发生。

猜你喜欢

转载自blog.csdn.net/weixin_42752435/article/details/81175396