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语法产生的拷贝

Copy constructor拷贝构造函数

时间: 2024-08-25 22:19:30

Copy constructor拷贝构造函数的相关文章

C++ Copy Constructor in depth (深入理解C++拷贝构造函数)

The copy constructor is a special kind of constructor which creates a new object which is a copy of an existing one, and does it efficiently. (拷贝构造函数是一种特别的构造函数,用于复制已经存在的对象到新生成的对象,这是一种高效的方式.) Here below is a simple declaration of a copy constructor: (

(C++)关于拷贝构造函数 Copy Constructor

题目: In which of the following scenarios is a Copy Constructor called or invoked? A.    When no conversion function exists for converting the class object to another class object B.    When an existing object is assigned an object of its own class C. 

const成员-拷贝构造函数(copy constructor)

const成员 const成员:被const修饰的成员变量.非静态成员函数 必须类里面初始化 class Car { public: const int m_price = 0; //const常量设置 Car() :m_price(0) {} //也可以在构造函数中初始化 void run() const { //const一般写最后 cout << "run()" << endl; m_price = 10; } }; const成员函数 两个同名函数构成了

关于 C++ 拷贝构造函数(copy constructor)中的形参必须为引用类型的详解

在<C++ primer>中文第四版中,关于拷贝构造函数(也称复制构造函数)是这样定义的:是一种特殊构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用. 问题来了!为什么形参必须为该类类型的引用?而不能是值传递方式?(PS:其实传值和传址都可以统一为传值,前者传的是对象的值,后者传的是对象的地址的值) 先看下边两组代码: 1. 1 class Example { 2 3 public: 4 5 Example() {} 6 7 Example(const Example&a

15.含有指针成员的类的拷贝(copy constructor)

http://zhedahht.blog.163.com/blog/static/25411174200722710364233/ http://www.cnblogs.com/t427/archive/2012/08/10/2633133.html http://blog.csdn.net/gamecreating/article/details/5382902 http://www.cppblog.com/xczhang/archive/2008/01/21/41569.html 题目:下面

构造函数语义学&mdash;&mdash;Copy Constructor的建构操作

?在三种情况下,会以一个object的内容作为另一个class object的初值: object明确初始化 123 class {...};X x;X xx = x; object被当作参数交与某个函数 12345 extern void foo(X x);void bar(){ X xx; foo(xx);} 函数返回值是一个class object 12345 X foo_bar(){ X xx; ... return xx;} 如果开发者已经明确定义了一个copy constructor

【C/C++学院】(6)构造函数/析构函数/拷贝构造函数/深copy浅copy

1.构造函数 类的初始化即为构造函数.也为:隐式的初始化. 构造函数在对象初始化的时候,自动被调用.隐式的调用. 构造函数分为三种:有参构造函数.无参构造函数.拷贝构造函数. 有参构造函数调用有三种:括号法.等号法.手工法. #include <iostream> using namespace std; class Test { private: int m_a; public: Test()//无参构造函数 { } Test(const Test &obj)//拷贝构造函数 { }

C++类的拷贝构造(Copy constructor)函数隐藏陷阱

好长时间没写C++了,今天写了个很简单的String类,竟然调试了半天,最终发现了一个十分隐蔽的陷阱,写出来供大家分享. C++中类的拷贝构造函数的作用就是通过类的一个对象来实例化另一个对象.下面是我写的一个MyString类,头文件MyString.h: #include <iostream> using namespace std; class MyString { public: MyString(); MyString(const MyString& str);//这里就是我们

拷贝构造函数[c++]

拷贝构造函数何时会被调用? 1. 对象以值传递的方式传入函数参数 2.对象以值传递的方式从函数返回 3.对象需要通过另外一个对象进行初始化 下面我们来看代码: //#include <iostream> //using namespace std; //template <typename T, int MAX> //T:队列的类型,char,int,double,包括自己的struct .MAX:循环队列的最大长度 //class Queue //{ //private: //