Skip to content

Commit

Permalink
修改
Browse files Browse the repository at this point in the history
  • Loading branch information
arkingc committed Jul 26, 2018
1 parent 5d622c0 commit cd94e15
Showing 1 changed file with 53 additions and 8 deletions.
61 changes: 53 additions & 8 deletions C++/C++对象模型.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,36 @@

## 2.1 默认构造函数的构造操作

有4种情况,会造成“编译器必须为未声明构造函数的类合成一个默认构造函数”
考虑如下代码;

```c++
class Foo {
public:
int val;
Foo *pnext;
};

void foo_bar()
{
//程序要求bar's members都被清为0
Foo bar;
if(bar.val || bar.pnext)
// ... do something
// ...
}
```
上述代码是否会合成默认的拷贝构造函数?首先需要提到2个需要:
* **编译器的需要**
* **程序员的需要**:上述代码就是“程序员的需要”,在这种情况下,为成员执行初始化应该是程序员的责任
那么在来考虑是否会合成拷贝构造函数:
* 在**C++ Annotated Reference Manual(ARM)**中:只有编译器需要时才会合成,所以上面的代码不会合成默认的拷贝构造函数
* **C++ Standard**:如果没有任何用户声明的构造函数,那么会有一个默认构造函数被隐式声明,但是这样被隐式声明的默认构造函数的trival(浅薄无能,没啥用)constructor。只有当一个默认构造函数是nontrivial时,才会被合成出来。所以,如果按照C++标准,上面的代码会声明一个trivial的默认构造函数,但是因为是tivial,所以并不会合成
有4种情况,会造成“编译器必须为未声明构造函数的类合成一个默认构造函数”,即nontrivial的默认构造函数:
1. **类包含带有默认构造函数的成员**
2. **类继承自带有默认构造函数的基类**
Expand All @@ -47,19 +76,33 @@
* 类派生自一个继承串链,其中有一个或更多的虚基类
4. **类带有一个虚基类**
被合成出来的构造器只能满足编译器(而非程序)的需要,它之所以能够完成任务,是借着“调用成员对象或基类的默认构造函数”或是“为每一个对象初始化其虚函数机制或虚基类机制”而完成的
**被合成出来的构造器只能满足编译器(而非程序)的需要**,它之所以能够完成任务,是借着“调用成员对象或基类的默认构造函数”或是“为每一个对象初始化其虚函数机制或虚基类机制”而完成的
**在合成的默认构造函数中,只有基类子对象和类成员对象会被初始化。所有其它的nonstatic数据成员(如整数、整数指针、整数数组等等)都不会被初始化。这些初始化操作对程序而言或许有需要,但对编译器则非必要**。如果程序需要把一个“把某指针设为0”的默认构造函数,那么提供它的人应该是程序员
在合成的默认构造函数中,只有基类子对象和类成员对象会被初始化。所有其它的nonstatic数据成员(如整数、整数指针、整数数组等等)都不会被初始化。这些初始化操作对程序而言或许有需要,但对编译器则非必要。如果程序需要把一个“把某指针设为0”的默认构造函数,那么提供它的人应该是程序员
> 总的来说就是,按照C++ Standrand,如果用户没有声明,那么会隐式的声明一个,但是会不会合成取决于声明出的是trivial还是nontrivial
## 2.2 拷贝构造函数的构造操作
* **默认逐成员初始化(Default Memberwise Initialization)**:
* **逐成员初始化(Memberwise Initialization)**:
* **位逐次拷贝(Bitwise Copy Semantics)**
* **位逐次拷贝(Bitwise Copy Semantics)**
```c++
//以下声明展现了bitwide copy semantics
class Word{
public:
Word(const char*);
~Word() { delete [] str;}
//...
private:
int cnt;
char *str;
}
```
在用一个类X的对象a初始化这个类的对象b时,内部采用的初始化就是**默认逐成员初始化**。具体来说,就是把a的数据成员一个个单独拷贝到b中
如果类X没有显式的拷贝构造函数,那么在用一个类X的对象a初始化这个类的对象b时,内部采用的初始化就是**默认逐成员初始化**。具体来说,就是把a的数据成员一个个单独拷贝到b中。如果类X里面还包含有成员类对象(Member Class Object),如类Y的对象,那么此时就不会把a的成员类对象拷贝到b中,而是递归的进行**逐成员初始化**,**逐成员初始化**用的就是**位逐次拷贝**和**拷贝构造函数**(Copy Constructor)
如果类X里面还包含有成员类对象(Member Class Object),如类Y的对象,那么此时就不会把a的成员类对象拷贝到b中,而是递归的进行**逐成员初始化****逐成员初始化**用的就是**位逐次拷贝****拷贝构造函数**(Copy Constructor)
**就像默认拷贝构造函数一样,C++ Standard上说,如果class没有声明一个拷贝构造函数,就会有隐式的声明出现。C++ Standard把拷贝构造函数分为trivial和nontrivial两种。只有nontrivial的实例才会被合成于程序之中。如果展现出”bitwise copy semantics“(位逐次拷贝语意),那么拷贝构造函数就是trivial的**
**如果一个类没有定义显示的拷贝构造函数,那么编译器是否会为其合成取决于类是否展现“位逐次拷贝”**:
Expand All @@ -72,7 +115,7 @@
前2种情况中,编译器必须将成员或基类的“拷贝构造函数调用操作”安插到被合成的拷贝构造函数中
第3种情况不展现“位逐次拷贝”是因为需要正确的处理虚函数指针vptr。1)如果使用子类的一个对象初始化另一个子类的对象,可以直接考“位逐次拷贝”完成;2)但假设用一个子类对象来初始化一个父类对象(会发生切割行为),父类对象的虚函数指针必须指向父类的虚函数表vtlb,如果使用“位逐次拷贝”,那么父类的虚函数指针会执行子类的vtlb
第3种情况不展现“位逐次拷贝”是因为需要正确的处理虚函数指针vptr。1)如果使用子类的一个对象初始化另一个子类的对象,可以直接靠“位逐次拷贝”完成;2)但假设用一个子类对象来初始化一个父类对象(会发生切割行为),父类对象的虚函数指针必须指向父类的虚函数表vtlb,如果使用“位逐次拷贝”,那么父类的虚函数指针会执行子类的vtlb
<div align="center"> <img src="../pic/cppmode-2-1.png"/> </div>
Expand All @@ -87,6 +130,8 @@ Raccoon *ptr;
Raccoon little_critter = *ptr;
```

> 总的来说就是,按照C++ Standrand,如果用户没有声明,那么会隐式的声明一个,但是会不会合成取决于声明出的是trivial还是nontrivial

## 2.3 程序转换语意学

```c++
Expand Down Expand Up @@ -122,7 +167,7 @@ int main(){

**编译器会对初始化列表一一处理并可能重新排序,以反映出成员的声明顺序。它会安插一些代码到构造函数体内,并置于任何显示用户代码(explicit user code)之前**

> 相关问题:是否可以使用memcpy来拷贝一个class对象,并解释原因
> 和默认构造函数、拷贝构造函数相关的问题:是否可以使用memset来初始化一个对象、使用memcpy来拷贝一个对象?只有在”class不含任何由编译器产生的内部members“时才能有效运行。如果class声明一个或一个以上的virtual functions,或内含一个virtual base class,那么使用上述函数将会导致那些”被编译器产生的内部members“的初值被改写
<br>
<br>
Expand Down

0 comments on commit cd94e15

Please sign in to comment.