GeekBand C++面向对象高级编程(下) Second Week
本周主要是讲述了C++的对象模型,通过对于对象模型的深入了解,我们可以知道虚函数,多态,this指针的了解。 以及部分 new,delete的部分详细介绍。
Object Model
C++的对象模型是C++对象最重要的部分之一,且包含的内容非常的丰富.C++中的class是由data member和function member组成的。对象模型的内存布局和data member和function member紧密相关。这里主要总结下function member及相关继承关系对与object model的影响。
C++支持三种类型的member function: static,nonstatic 和 virtual。
static member function
静态成员函数有有以下的调用方式。
obj.func();
ptr->func();
Object::func(); // 这种member selection的写法是一种写法上的便利,它会被转化成一个直接的函数调用
静态成员函数本质上和非成员函数是一样的(编译器会做一些 name mangled的处理)。如果取一个static member function的地址,获得的将是其在内存中的地址,其地址类型并不是一个“指向 class member function的指针”,而是一个“nonmember函数指针”。
静态成员函数有以下特征:
1.它不能够直接存取class中的nonstatic members
2.它不能够声明为const,volative,virtual
3.它不需要经由class object才被调用
nonstatic member function
C++的设计准则之一就是:非静态成员函数至少必须和一般的非成员函数有相同的效率。
编译器在内部会将非静态成员函数通过 name mangling的处理成对等的非成员函数。通常可能有以下几个步骤:
1.改写函数签名, 添加一个额外的this指针的参数。
2.将每一个 “对nonstatic data member的存取操作” 改为竟有this指针来存取
3.将非静态成员函数重新写成一个外部函数,该函数名经过“name mangling” 处理,使它成为独一无二的函数名。
virtual member function
C++中虚函数加上继承是实现多态的手段。
一个类中如果有虚函数,编译器在类的构造函数中会加入一个虚函数指针(vptr)的初始化。vptr指向一个虚函数表(vtbl). vtbl中存放着相应虚函数的对应的函数地址。
class Point {
public:
virtual ~Point();
virtual float x() const;
private:
float x_;
};
class Point2d : public Point{
public:
virtual float y() const;
float length() const;
private:
float x_, y_;
};
类的内存布局如下:
Point* ptr = new Point();
ptr->x();
这个会被转化为
(ptr->vptr[2])(ptr)
所以Point*类型的变量,会根据指向不同的类(基类或子类)的实例,根据vptr指向的对应slots的函数地址,执行不同的函数,从而实现多态。
multi inheritance
多重继承的情况要比单继承要复杂。
class base1 {
public:
virtual base1();
};
class base2 {
public:
virtual ~base2();
};
class devired : public base1, public base2 {
};
相关类的内存布局如下:
从内存的布局可以看出,子类分别继承了基类的虚函数表,但是也不是完全的独立继承。继承的两个虚函数表有不同的地位。一般按照继承的顺序,第一个(base1)基类中的虚函数和第二个基类(base2)中的相关虚函数。
virtual inheritance
虚继承
为了解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置为虚基类。这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。这样不仅就解决了二义性问题,也节省了内存,避免了数据不一致的问题。
class 派生类名:virtual 继承方式 基类名
virtual是关键字,声明该基类为派生类的虚基类。
在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧跟其后的基类起作用。
声明了虚基类之后,虚基类在进一步派生过程中始终和派生类一起,维护同一个基类子对象的拷贝
1.在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧跟其后的基类起作用。
2.声明了虚基类之后,虚基类在进一步派生过程中始终和派生类一起,维护同一个基类子对象的拷贝。
3.观察类构造函数的构造顺序,拷贝也只有一份。
new 与 delete
对于class,new 是先分配内存,再调用ctor。delete是先调用dtor,然后再释放内存。
String* ps = new String("hello");
// 上面这句话转化为下面的三句话
void* mem = operator new(sizeof(String));
ps = static_cast<String*>(mem);
ps->String::String("hello);
其中operator new 分为全局的和属于某个类的区分。可以通过重载operator new(operator new[]) 或 operator delete(operator delete[]) 函数来自己控制内存的管理。
重载 ::operator new 等
重载全局的 operator new 等函数是一件非常危险的事情,它会影响到所有的 new 等的内存分配。
void* myAlloc(size_t size)
{ return malloc(size); }
void myFree(void* ptr)
{ return free(ptr); }
// 全局的函数不可以声明于任何一个 namespace 内
inline void* operator new(size_t size)
{ cout << "my global new" << endl; return myAlloc(size); }
inline void* operator new[](size_t size)
{ cout << "my global new[]" << endl; return myALloc(size); }
// delete 同理
重载 member operator new/delete
对于某个类重载其operator new/delete的函数,可以只针对这个类使用自定义的内存管理。
class Foo {
public:
void* operator new(size_t);
void operator delete(void*, size_t);
// ...
};
在使用new或delete时可以指定使用全局的 operator new 或operator delete 函数
// 如果没有定义member的operator new的方法,就调用global的方法,有就调用自定义的member的版本
Foo* pf = new Foo;
// 强制使用 global的版本
Foo* pf = ::new Foo;
重载 new(), delete()
我们可以重载 class member operator(),写出多个版本,前提是每一个版本都必须有特殊的参数列,其中第一个参数必须是size_t,其余参数以 new 所制定的placement argument为初值。出现于 new(...)小括号内的便是所谓的 placement arguments。这种写法被称为placement new。
我们也可以重载class member operator delete(),写出多个版本。但是他们绝不会被delete调用。只有当 new 所调用的 ctor 抛出 exception, 才会调用这些重载版的 operator delete()。它只可能这样被调用,主要用来归还未能完全创建成功的object所占用的memory。
const
const是一个非常好的关键字,如果可以用,就需要加上。
const可以用来修饰函数(只限成员函数)和 函数的参数。修饰成员函数表示此函数保证不更改data members。
关于const object 和 non-const object 调用 const 和 non-const 函数的关系可以用以下的表格来表示。
const object | non-const object | |
---|---|---|
const member function | ✅ | ✅ |
non-const member function | ❎ | ✅ |
同时const也是属于成员函数的签名的一部分,一个类中可以同时出现nonconst 和const两种版本。
当成员函数的const和non-const版本同时存在时,const object只能调用 const的版本,non-const object 只能调用non-const 的版本。