学习《 C++对象模型》 侯捷著
极力推荐Adoo写的 小结: http://www.roading.org/category/shen-du-tan-suo-cdui-xiang-mo-xing.html
implicit 暗中的,隐含的 explicit 明确的 trivial 没有用的 non-trivial 有用的 memberwise 对每个member进行.. 操作 bitwise 对每个bit进行 ... 操作 ( bitwise copy 位拷贝操作) semantics 语意
第一章 关于对象
1. C程序员的巧计有时候会成为C++程序员的陷阱。 例如把单一元素的数组,放在一个struct的尾端,于是每个struct objects可以拥有可变大小的数组:
struct mumble{
int nTypes;
char pc[1];
};
struct mumble *pMum = (struct mumble *)malloc( sizeof( struct mumble) + strlen(string ) +1); //string是个很大的字符串,例如“abcdefgheilaieiijf......"
第二章 构造函数语意学
2.1 default constructor 在被编译器需要的时候,产生出来。什么时候被需要?
四种情况下会合成 implicit nontrivial default constructors, :
a. 带有default constructor 的 member class object
b. 带有 default constructor 的 Base Class
c. 带有 virtual function的class
d. 带有 vitual base class的子类
至于没有存在这4种情况而又没有声明任何constructor的classes, 我们说它们拥有的是 implicit trivial default constructors, 他们实际上并不会被合成出来。
证明 a. 带有default constructor 的 member class object, 编译器会合成default constructor:
//a.cpp
class Foo
{
public:
int val;
Foo *pnext;
};
int main()
{
Foo bar;
std::cout << bar.val << std::end; // bar.var 和 bar.pnext 是随机数,没被初始化为0
return 0;
}
gcc -o a a.cpp;
nm -C a | grep Foo
输出结果为空。
//a.cpp
class Foo
{
public:
int val;
Foo *pnext;
string str; //新增加的成员变量, string类有explict default constructor
};
int main()
{
Foo bar;
std::cout << bar.val << std::end; // bar.var 和 bar.pnext 是随机数,没被初始化为0
return 0;
}
gcc -o a a.cpp;
nm -C a | grep Foo (或者 nm a | grep Foo| c++filt )
00000000004008f8 W Foo::Foo()
0000000000400916 W Foo::~Foo()
上面的default constructor 和 destructor 函数就是编译器为我们合成的。
2.2 copy Constructor的构造操作,什么时候被创建?
有三种情况下会用到copy constructor:
a. class X {....};
X x;
X a = x; // 有新对象实例生成会用到 copy constructor。 ( 如果没有新对象实例生成,会使用 operator =(), 例如 X x1, x2; x1 = x2;)
b. 作为形参
extern void foo(X x);
void bar() {
X a;
foo(a); //使用 copy constructor
}
c. 作为函数返回值
X foo_bar()
{
X a;
........
return a;
}
和default constructor一样,如果class没有声明一个copy constructor,就会有隐含的声明. C++ 把copy construstor区分为 trivial 和 non-trivial两种。 只有non-trivial,编译器才会合成copy constructor到代码中。 但有一点需要注意,不管是 trivial 和 non-trivial的copy constructor,都会把整数,指针,数组等等的nonclass members 进行复制。
什么时候,才会合成copy constructor?
四种情况:
a. 当class 内含一个member object, 而这个object声明了一个non-trivial copy constructor。
b. 当class继承自一个base class, 而且 base class 存在一个 non-trivial copy constructor。
c. 当class声明了一个或者多个 virtual functions。
d. 当class 的父类中,有 virtual base class 时
证明:
//c.cpp
#include <iostream>
#include <string>
using namespace std;
class Test
{
public:
Test(int i)
{
len = i;
}
public:
char *str;
int len;
};
int main()
{
Test a(5);
Test b=a;
Test c(10);
Test d = b;
std::cout << b.len << std::endl;
std::cout << c.len << std::endl;
std::cout << d.len << std::endl;
return 0;
}
g++ -o c c.cpp; ./c 输出结果为:
5
10
5
sundh@SSDEV016:~$ nm -C c | grep Test 输出结果
0000000000400a0e W Test::Test(int) //只有构造函数,没有 copy constructor,
修改:
#include <iostream>
#include <string>
using namespace std;
class Test
{
public:
Test(int i)
{
len = i;
}
public:
char *str;
int len;
string a; //增加 string a成员变量, string是有non-trivial copy constructor。
};
int main()
{
Test a(5);
Test b=a;
Test c(10);
Test d = b;
std::cout << b.len << std::endl;
std::cout << c.len << std::endl;
std::cout << d.len << std::endl;
return 0;
}
sundh@SSDEV016:~$ nm -C c | grep Test 输出结果
0000000000400bb4 W Test::Test(int)
0000000000400bfe W Test::Test(Test const&) //合成了copy constructor
0000000000400be0 W Test::~Test()
2.3 构造函数初始化列表
什么时候必须使用 初始化列表(member initialization list):
a. 当初始化一个reference member时
b. 当初始化一个 const member时
c. 当调用一个base class 的constructor,而它拥有一组参数时(它没有 default constructor).
d. 当调用一个member object的constructor,而它拥有一组参数时(它没有 default constructor).
陷阱:
class Word {
public:
Word(){ //大括号里面的内容,叫做explicit user code
strName = 0; //会调用copy constructor, 但还会调用default constructor 吗?
nCnt = 0;
}
private:
string strName;
int nCnt;
};
//default constructor 扩展出来的 C++伪码
Word::Word(){
strName.string::string(); //调用string的 default constructor
string temp = string(0); //新创建temp变量,调用string::string(int) 构造函数,然后调用copy constructor
strName.string::operator=(temp); // 调用string的 operator =() 操作符
temp.string::~string(); // 调用析构函数
nCnt = 0;
}
编译器会一一操作初始化列表(initialization list),以适当的次序在constructor之内安插初始化操作,并且在任何explicit user code(代码里面有说明)之前。 初始化列表中成员变量赋值顺序,有class中的声明顺序决定,而不是在list中的排列顺序。
总结:编译器会背着程序员干很多事情,大致顺序为:
a. 构造函数先调用base的构造函数,后设置其他变量,包括vptr;(相对于子类来说,如果base类有default construstor,后最先调用base的default construstor; 如果没有,需要程序员在子类的初始化列表显示调用base的带参数的construstor )
b. 构造函数初始化列表。 (如果列表里面包含了 base的构造函数, 那上面步骤a是初始化列表中第一个被执行的。 )
c. 构造函数调用虚函数,会调用它的vptr设置的虚函数,也就是说vptr的该虚函数。( this->__vptr = __vtbl_pointer; )
d. 执行explicit user code (前面代码里面有说明)。
可以参考:C++ - 对象模型之 构造和析构函数都干了什么 http://blog.youkuaiyun.com/gykimo/article/details/8629896
第三章 Data语意学 和 第四章 Function语意学
类有两种data member类型:non-static 和 static, 有三种 function member 类型: non-static , static 和 virtual。
继承中,有一种virtual base的虚继承。
http://blog.youkuaiyun.com/sunny04/article/details/41700437 这篇文章详细讲解了,我就不赘述了。
需要注意的是:
1. BaseClass *temp = new SubClass(); 这样操作,不仅仅是data member发生切割现象, vptr函数表也会发生切割现象, 只保留BaseClass的那一部分。
2. C++的设计标准之一是 类的nonstatic member function 需要和 一般的 non-member function效率相同。 如果确保这一点呢? 编译器将non-static member function 重新写成一个non-member function, 对函数名称进行 ‘mangling'处理,使它在程序中独一无二。
class A{...
int myPrint();
};
A *a = new A();
a->myPrint() ; //myPrint 这里是nonstatic 函数。 这行代码,会被编译器处理成: myPrint_1AEv( this); 变成了一个non-member function,会隐形插入一个this形参。
3. virtual member function 会被编译器这样处理:
a->myPrint() ; // myPrint 这里是virtual函数。 这行代码,会被编译器处理厂: (* a->vptr[1])(a); myPrint函数被slot的index取代
第五章 构造,解构,拷贝,语意学