Copy constructor拷贝构造函数

翻译的是wikipedia关于copy constructor,地址:点击打开链接

        拷贝构造函数是C++语言中用一个已有对象创建一个新对象的特殊构造函数,该函数的第一个参数必须是函数所在类型的引用(译注:const/non-const都可以,可以有多个参数剩余参数必须有默认值,一定要是引用,这些原因见后,问:有多个参数拷贝构造如何调用?).
通常编译器会自动为每一个class创建一个拷贝构造函数(显示拷贝构造);有些情况下程序员自定义了拷贝构造函数(用户自定义拷贝构造),这时编译器不合成拷贝构造函数。因此,总是有一个拷贝构造函数要么是用户自定义或者编译器生成。
        通常当一个对象内含指针或者非共享的引用(如文件描述符)需要一个用户自定义的拷贝构造函数,这时需要一个用于自定的析构函数和赋值函数(译注:The big three三大件)


定义:
        可以通过拷贝构造函数和赋值函数实现拷贝,拷贝构造函数的第一个参数是所属对象的引用(可能是const或者volatile),拷贝构造函数可能有多个参数,但是剩余的参数必须有默认值。下面是合法的关于class X的拷贝构造函数定义:

X(const X& copy_from_me);
X(X& copy_from_me);
X(volatile X& copy_from_me);
X(const volatile X& copy_from_me);
X(X& copy_from_me, int = 0);
X(const X& copy_from_me, double = 1.0, int = 42);
...
      尽量使用第一个拷贝构造形式除非有更好的理由使用剩下的拷贝构造函数,第一个和第二个的区别是第一个拷贝构造函数可以接收一个临时对象作为其拷贝源,例如:

X a = X();     // 第一个X(const X& copy_from_me)合法,第二个X(X& copy_from_me)则非法
               // 因为第二个想要一个non-const X&来创建a,但是编译器调用默认构造函数创建一个临时对象再利用拷贝构造生成临时对象的一份拷贝,对于有些编译器上述采用第二种拷贝构造函数不会报错,但是这是不可移植的(非标准)
             
      另外一个显然的区别是:
const X a;
X b = a;       // X(const X& copy_from_me)是合法的,X(X& copy_from_me)是不合法的,因为后者希望一个non-const X&

       第二种拷贝构造形式在需要修改拷贝源X&的情况下使用,这种清醒非常罕见,但是在标准库的智能指针要转让其所有权的时可能需要这种语义,如:

std::auto_ptr拷贝构造函数必须是non-const &:
std::auto_ptr<type> a;//译注
std::autor_ptr<type> b=a;//a将所有权转让给b The following are invalid copy
     拷贝构造函数第一个参数必须是引用:
X a;
X b=a;//拷贝构造合法的前提的其第一个参数必须是引用
     以下是非法的拷贝构造函数,原因是第一个参数不是引用:
X(X copy_from_me);
X(const X copy_from_me);
         因为调用这些构造函数同样需要进行对参数的拷贝,这样会导致无穷递归调用。
一下情形可能是导致拷贝构造的调用:
                1. 当函数返回一个对象时,//译注:X fun();
                2. 当函数参数是个对象时//译注:void fun(X a);参数是非引用
                3. 当异常抛出一个对象时
                4. 当捕获一个对象时
                5. 当对象置于列表初始化中
      这些情形下的拷贝语义等价于
T  x=a;
     但是这些情形不能保证必须调用拷贝构造函数,因为c++标准允许编译器针对拷贝进行某些优化,一个典型的例子就是返回值优化(RVO)
译注:RVO可能是如下样子:
X fun(){
	X a;
	....;//针对a的操作
	return a;
}
      编译器优化可能变为:
X _result;
void fun(X& _result){
	...;
	return;
}

     操作:

     对象的赋值有两种方法:

           通过表达式显示的赋值
           初始化


      通过表达式显示赋值
Object a;
Object b;
a = b;       // 转化为Object::operator=(const Object&),因此调用A.operator=(B)进行复制,而非拷贝构造

      初始化:
           一个对象可通过一下方式初始化:
                   a. 声明即初始化
Object b = a; //转化为Object::Object(const Object&)调用拷贝构造函数
                  b.函数传递参数
type function(Object a);
                  c. 函数返回对象
Object a = function();
        拷贝构造函数只用于初始化,不会应用于赋值(赋值是调用assignment operator)
        继承条件下,拷贝构造函数调用基类拷贝构造函数,并将基类成员通过适当方式拷贝这些成员到目的地:若成员是class则调用成员的copy constructor,若成员是标量类型(int,float,...)则调用内置的operator =,最后,若成员是一个数组则数组每个元素都将被拷贝
        用户自定义拷贝构造函数要定义拷贝对象执行的操作

例子:
       以下例子介绍了拷贝构造函数怎么工作及为什么要调用它们:
       显示拷贝构造函数:
               让我们先看如下例子:

#include <iostream>
 
class Person {
public:
    int age;
 
    explicit Person(int a) 
        : age(a)
    {
    }
};
 
int main() 
{
    Person timmy(10);
    Person sally(15);
 
    Person timmy_clone = timmy;
    std::cout << timmy.age << " " << sally.age << " " << timmy_clone.age << std::endl;//10 15 10
    timmy.age = 23;
    std::cout << timmy.age << " " << sally.age << " " << timmy_clone.age << std::endl;//23 15 10
}
        正如预期一样,timmy拷贝进了新对象timmy_clone,当timmy的成员age改变了,timmy_clone的成员age仍保持不变,这是因为它们是完全不同的对象
编译器为我们生成了一个默认拷贝构造函数,它可能像如下样子:
Person(const Person& other) 
    : age(other.age) // calls the copy constructor of the age,内置类型是operator =直接赋值
{
}
      因此,我们什么时候需要自定义一个拷贝构造函数呢?下一节将介绍


     用户自定义拷贝构造函数:
现在,考虑一个非常简单的内含动态数组类像如下:

#include <iostream>
 
class Array {
public:
    int size;
    int* data;
 
    implicit Array(int sz) 
        : size(sz), data(new int[size])
    {
    }
 
    ~Array() 
    {
        delete[] this->data;
    }
};
 
int main() 
{
    Array first(20);
    first.data[0] = 25;
    {
        Array copy = first;
        std::cout << first.data[0] << " " << copy.data[0] << std::endl;
    }    //25 25 (1)
    first.data[0] = 10;    //segment fault (2)
}
        因为我们没有显示定义一个拷贝构造函数,编译器将会为我们生成一个,生成的构造函数可能像这样:
Array(const Array& other)
  : size(other.size), data(other.data) {}
        问题在于这个拷贝构造对指针data执行浅拷贝(仅拷贝指针的内容),这意味着两个对象的成员data都指向同一片内存区域,这不是我们预期的。当程序执行到(1),对象copy的析构函数调用(栈对象的生命周期与其所在的域相关),Array的析构函数删除了copy.data所指向的内存,因为first.data和copy.data都指向的相同内存,所以到(2)时first.data是个空悬指针,存取它导致了段错误。
       如果我们自定义构造函数为深拷贝,这个问题就消失了:
// for std::copy
#include <algorithm>
 
Array(const Array& other)
    : size(other.size), data(new int[other.size]) 
{
    std::copy(other.data, other.data + other.size, data); 
}
        这里我们创建了一个新的int数组并从源对象拷贝了元素过来,现在other的析构函数删除的其自己的内存,不是first.data的,此时(2)不会再有段错误。
这里有些优化策略替代立马进行深拷贝,这些策略允许你安全的使用不同对象的共享数据(共享用以节省空间),copy on write(写时拷贝)策略使得当需要写入的时候才进行数据的拷贝;引用计数是对数据被多少对象持有的计数,当计数为0时将删除计数(如std::share_ptr)


       拷贝构造和模板:
       和预期相反,一个模板拷贝构造函数不是用户自定义拷贝构造函数。如下这样定义是不够的:

template <typename A> Array::Array(A const& other)
    : size(other.size()), data(new int[other.size()]) 
{
    std::copy(other.begin(), other.end(), data);
}
       如果模板参数A是一个数组Array,则需要一个用户自定义的专门针对从Array到Array的非模板拷贝构造函数(模板特化)


      位拷贝构造:
      位拷贝构造函数执行为对象中所有元素简单的variable-by-variable赋值操作,位拷贝构造因此称为从原始对象到新对象的bit-by-bit拷贝。
 
       可以从上图清晰看出位拷贝构造使得原始对象和新对象都指向了同一变量,因此当一个对象的p改变后两个对象都改变


       Logical copy constructor产生一份真是的拷贝即使是动态数据结构,如下图:
 
       从上图可以看出一个新的动态成员变量被创建(这里是new)并拷贝了原始对象的值

       显示拷贝构造:
       显示拷贝构造使用关键字explicit,例如:

explicit X(const X& copy_from_me);
       这用来阻止函数调用中或copy-initializetion语法产生的拷贝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值