1.关于vptr和vtbl
之前的学习已经了解到c++多态主要靠虚函数实现,如果说c++的class在实现上相比c的struct有什么开销的话,那么虚函数表(vtbl)的维护和每个对象实例里虚表指针(vptr)将是比较明显的开销。
对于如下三个类
class A {
public:
virtual void vfunc1() { cout << "A::vfunc1\n"; }
virtual void vfunc2() { cout << "A::vfunc2\n"; }
void func1() { cout << "A::func1\n"; }
void func2() { cout << "A::func2\n"; }
};
class B : public A {
public:
virtual void vfunc1() { cout << "B::vfunc1\n"; }
void funcb() { cout << "B::funcb\n"; }
};
class C : public B {
public:
virtual void vfunc1() { cout << "C::vfunc1\n"; }
void funcc() { cout << "C::funcc\n"; }
};
非虚成员函数:A::func1(),A::func2(),B::funcb(),C::funcc()会单独在内存里存一份
虚成员函数:A::vfunc1(),A::vfunc2(),B::vfunc1(),C::vfunc1()也会单独存一份,但是这四个虚函数会由虚函数表来记录,由于这个例子里有三个类,因此内存里会有三份虚函数B::vfunc1(),A::vfunc2(),表,我们假设它们为A,B,C表。 A表里会有两个指针,分别指向A::vfunc1(),A::vfunc2()的地址,B表里两个指针,分别指向B::vfunc1(),A::vfunc2(),同理,C表里的指针指向C::vfunc1(),A::vfunc2()。
对于用基类指针new子类的情况:A pa = new B; 这个实例对象里放的也是B类对应的虚函数表,因为编译器做了个向上转型(upcasting)。
其实理解了虚函数表在内存的形式后,调用虚函数的代码可以这么表示: ((pa->vptr)[n])(pa) 因为第一个参数肯定是*this。
2.动态绑定
为了C++的多态性,是有动态绑定和静态绑定这两种说法的:
静态绑定:绑定的对象是静态类型,也就是编译期就能决定的,是确定的,不会更改的,比如 A a; a的内容虽然会在运行期发生改变,但是a就是a,这点是不会变的。
动态绑定:绑定的对象是动态类型,动态类型就是指在编译期无法决定的,因为它可能在运行期发生改变,比如指针:A* pa; pa可以在运行时重新指向其他对象,或者转型指向B类或者C类。
3.const
const对象不能调用non-const函数,只能调用const函数。
4.关于new,delete和重载
new和delete也是常用的,新申明的数组需要使用内存时,需要new一段size大小的空间,当使用结束时delete掉,即释放内存,他俩是配合着使用的,不能忘写其中一个。
全局的new有六种重载形式:
void *operator new(std::size_t count)
throw(std::bad_alloc); //一般的版本
void *operator new(std::size_t count, //兼容早版本的new
const std::nothrow_t&) throw(); //内存分配失败不会抛出异常
void *operator new(std::size_t count, void *ptr) throw();
//placement版本
void *operator new[](std::size_t count) //
throw(std::bad_alloc);
void *operator new[](std::size_t count, //
const std::nothrow_t&) throw();
void *operator new[](std::size_t count, void *ptr) throw();
5.C++的各种new简介
1.new T
第一种new最简单,调用类的(如果重载了的话)或者全局的operator new分配空间,然后用类型后面列的参数来调用构造函数
2. new T[]
这种new用来创建一个动态的对象数组,他会调用对象的operator new[]来分配内存(如果没有则调用operator new,搜索顺序同上),然后调用对象的默认构造函数初始化每个对象
3.new()T 和new() T[]
这是个带参数的new,这种形式的new会调用operator new(size_t,OtherType)来分配内存
这里的OtherType要和new括号里的参数的类型兼容,这种语法通常用来在某个特定的地址构件对象,称为placement new,前提是operator new(size_t,void*)已经定义,通常编译器已经提供了一个实现,包含头文件即可,这个实现只是简单的把参数的指定的地址返回,因而new()运算符就会在括号里的地址上创建对象.需要说明的是,第二个参数不是一定要是void*,可以识别的合法类型,这时候由C++的重载机制来决定调用那个operator new.当然,我们可以提供自己的operator new(size_,Type),来决定new的行为
4. operator new(size_t)
这个的运算符分配参数指定大小的内存并返回首地址,可以为自定义的类重载这个运算符,方法就是在类里面声明加上void *operator new(size_t size)
5.operator new[](size_t)
这个也是分配内存,,只不过是专门针对数组,也就是new T[]这种形式,当然,需要时可以显式调用
6.operator new(size_t size, OtherType other_value)和operator new[](size_t size, OtherType other_value)
需要强调的是,new用来创建对象并分配内存,它的行为是不可改变的,可以改变的是各种operator new,我们就可以通过重载operator new来实现我们的内存分配方案.