Copy Constructor

有三种情况会使用到copy constructor

  • 用一个object初始化另一个object
  • 传参(pass by value)
  • 返回值

default memberwise initilization & bitwise copy semantics

link1link2link3

default memberwise initilization

如果我们没有为一个函数提供explicit copy constructor

那么当我们试图用一个object初始化另一个object的时候

内部就使用default memberwise initilization,然后defalut memberwise initilization又可以分为bitwise copy和default copy constructor

当遇到member class object的时候就会以递归的方式施行default memberwise initilization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class String {
public:
...
private:
char *str;
int len;
};

String str("hello")
String str2(str)

-------------String copy constructor---------
{
str2.str = str.str;
str2.len = str.len;
}
-------------String copy constructor----------

// if the class has the memebre class object

class Word {
public:
....
private:
int _count;
String _word;
};

那么Word会先拷贝_count然后再对_word实施memberwise initilization

就如同default constructor一样,一个default copy constructor的产生取决于它是否是trival,只有nontrival的instance会被生成

那么是否是trival就在于class是否展现出所谓的bitwise copy semantics

bitwise copy semantics

首先,我们要明确一个class什么时候不展现出bitwise copy semantics

  • 当class内含一个member object而且内含一个copy constructor时,无论是否被显式声明

  • 当继承自一个含有copy constructor(无论是否被显式声明)的class

  • 当class声明了一个或者多个virtual funciton

  • 当class派生自一个含有virtual base class的继承串联

那么,编译器就会弃用bitwise copy而使用default copy constructor

一般而言,如果只含POD数据成员,一般不会合成一个default copy construcotr

最需要注意的是,default copy constructor并不是深拷贝,因此如果涉及到资源的分配,就需要程序员设计一个explicit copy constructor

详情见link3

那么值得讨论的是,为什么以上四种情况不会展现bitwise copy semantics?

1.重新设定vptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A {
public:
virtual void get();
};

class B : public A {
public:
void get();
virtual give();
};

void func(A a) {}

B b;
func(b); //发生sliced

因为a的空间大小和b(b+a)的空间大小不同

因此需要将b中b的部分切掉,保留a的部分

此时,如果a中的vptr仍指向b的vtbl(因为bitwise),那么就会blow up

但是如果是指针或者引用,也就是void func (const A &a)则不会发生sliced,因为指针或者引用所引发的不是内存大小的改变,而是所指向内存的大小和内容的解释方式的改变

所以可以展现多态性,也就是可以将B传入func(const A &a)并调用B::get()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>

class A {
public:
virtual void get() {
std::cout << "A::get()" << std::endl;
}
};
class B : public A {
public:
void get() {
std::cout << "B::get()" << std::endl;
}
};
void func(A& a) {
a.get();
}
int main() {
B b;
A a;
func(b);
func(a);
return 0;
}

----output-----
B::get()
A::get()

因为不能使用bitwise的手法,所以default memberwise initilization就合成一个copy constructor,然后在里面显示设置vtpr

处理Virtual Base Class Subobject

还没弄懂virtual继承的对象模型,先留着

程序转化语义学

1.显式初始化操作

1
2
3
X x0;
X x1(x0);
X x2 = x0

这时候会有一个必要的程序转化

1.先声明,这样子其中的初始化操作会被剔除

2.插入class的copy constructor

1
2
3
4
5
X x1;
X x2;

x1.X::X(x0);
X2.X::X(x0);

2.参数的初始化

1
2
3
4
5
6
7
8
9
10
void func(X x0);
X x0;
func(x0);

----------------
X x0;
X _temp;
_temp.X::X(x0);
func(_temp); // void func(X& x0);
//在func返回后,_temp.X::~X()会被调用

另一种实现方式是用拷贝构建的方式把实际参数直接构建在其应该的位置上

在函数返回前,destructor会被调用

3.返回值的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
X x = bar();

X bar() {
X xx;
return xx;
}
----------------
X x;
bar(x)

void
bar ( X& _result ) {
X xx;
_result.X::X(xx);
return;
}

如果是函数指针也会进行转化

1
2
3
4
5
X (*pf) ();
pr = bar;
------------------
X (*pr) (X&);
pr = bar

Copy Constructor: 要还是不要

1
2
3
4
5
6
class Point3d {
public:
Point3d(float x, float y, float z) {}
private:
float _x, _y, _z;
};

如上所示,class里面的三个成员都是存储数值的

那么bitwise copy既安全又快速

那么我们就没必要设计一个copy constructor

实现copy constructor的最简单的方法如下

1
2
3
Point3d(const Point3d & rhs) {
memcpy(this, &rhs, sizeof(Point3d));
}

然而不管是使用memcpy or memset

前提条件都应该是class内不含编译器产生的内部member,如vptr

一旦涉及到vptr的改变,就不要使用

例如以下这个错误使用的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
class Pointer3d {
public:
Point3d(float x, float y, float z);
virtual ~Pointer3d();
};

Point3d::Point3d(float x, float y, float z) {
//编译器插入的内容,初始化vptr
_vptr_Point3d = _vtbl_Point3d;

//vptr清0!!!!!
memset(this, 0, sizeof(Point3d));
}

但如果涉及到资源的分配,例如指针获取资源

那么就需要我们设计一个copy constructor

这也就是何时使用深拷贝,何时使用浅拷贝的问题