Boolan C++ 笔记 五

一.对象模型(Object Model):关于虚指针和虚表

现给出两个类,我们从他们的内存构成来展开讨论

class Fruit{
   int no;
   double weight;
   char key;
public:
   void print() {   }
   virtual void process(){   }
};
   
class Apple: public Fruit{
   int size;
   char type;
public:
   void save() {   }
   virtual void process(){   }
};
以下是用来测试的代码
#include <iostream>
//#pragma pack(1)
using namespace std;
 
class Fruit{
    int no;
    double weight;
    char key;
public:
    explicit Fruit(int no_ = 0,double weight_ = 0,char key_='a' ):no(no_),weight(weight_),key(key_){}
    void print() {
        cout<<this<<' '<<&no<<' '<<&weight<<' '<<static_cast<void*>(&key)<<endl;
    }
    virtual void process(){ cout<<this<<'\n'<<&no<<'\n'<<&weight<<'\n'<<static_cast<void*>(&key)<<'\n'<<endl;  }
};
 
class Apple: public Fruit{
    int size;
    char type;
public:
    Apple(int size_=0, char type = 'a'){}
    void save() {   }
    virtual void process(){ Fruit::process ();cout<<&size<<'\n'<< static_cast<void*>(&type)<<'\n'<<endl; }
};
 
void getVtrlAddr(const  Fruit& fruit);
 
void getVtrlAddr (const Fruit &fruit) {
 
    printf("虚表地址:%p\n", *(int *)&fruit);
 
}
 
int main() {
    using namespace std;
    Fruit f;
    Apple a;
    cout<<"Fruit 内存地址排布:"<<endl;
    f.process ();
    cout<<"Apple 内存地址排布:"<<endl;
    a.process ();
    cout<<"f的size"<< sizeof (f)<<endl;
    cout<<"a的size"<< sizeof (a)<<endl;
    getVtrlAddr (f);
    getVtrlAddr (a);
 
 
    return 0;
}

得出结果(64位操作系统下)


以Fruit为例this的地址是0x7fff53ca4b28,第一个成员变量的no的地址0x7fff53ca4b30,差了8,说明0x7fff53ca4b28~0x7fff53ca4b29这块内存储存着一个数据,就是一个虚指针,在64位上的大小是8.因为该class有虚函数,所以有虚指针指向由虚函数构成的一个表.

我使用下列函数获取Fruit的虚指针,这个函数有效的前提存在虚指针.

void getVtrlAddr (const Fruit &fruit) {
 
    printf("虚表地址:%p\n", *(int *)&fruit);
 
}


继续观察打印的控制台信息,发现和上面说的内存对齐的原则是一样的.


至于为什么要对齐

大部分的参考资料都是如是说的:

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
上面的Fruit类和Apple类的结构我就画成如下


二.const

关于const考虑下图

需要注意的是当成员函数的const和non-const版本同事存在,const object只会调用const版本,non-const ovject 只会调用non-const版本.


关于const不同位置的意义:

char greeting[] = "Hello";
char *p = greeting;//non-const pointer,non-const data     p可以指向别处,*p也可以改变
const char*p = greeting;//non-const pointer ,const data   p可以指向别处,*p不可以改变
char *const p = greeting;//const pointer,non-const data   p不可以指向别处,*p可以改变
const char *const p = greeting;//const pointer,const data p不可以指向别处,*p也不可以改变


将某些东西声明为const可帮助编译器侦测出错误的用法.const可被施加于任何作用域内的对象,函数参数,函数返回类型,成员函数本体等.

编译器强制实施bitwise constness,但我们写程序时应该使用"概念上的常量性"(conceptual constness)

const 和non-const 成员函数中避免重复,如果二者有着实质上等价的实现时,令non-const版本调用const版本可避免代码重复

三.new和delete重载

首先,new和delete是运算符,重载new和delete是可能的.这样做的原因是,有时候希望使用某种特殊的动态内存分配方法.例如,可能有些子程序,他们的堆已经耗尽,自动开始把一个磁盘文件当虚存储使用,或者用户希望控制某一片存储空间的分配等.

重载new和delete的一般格式如下

void *operator new (size_t size)
{
   ......//完成分配工作
   return pointer_to_memory;
}

void operator dete(void *p)
{
  ......//释放由p指向的存储空间
}

重载new[] delete[]同上

注意:

new是先分配memory再调用ctor,delete是先调用dtor再释放memory.

array new 一定搭配array delete


还有一种member operator new/delete

形如 void * operator new(size_t); void operator delete (void*,size_t);void * operator new[](size_t); void operator delete[] (void*,size_t);


要想拒绝使用重载的new和delete函数,而使用全局的new和delete 请在new和delete前加"::" 就变成"::new","::delete".

我们可以重载class member operator new(),写出多个版本,前提是每一版本的声明都必须有独特的参数列,其中第一参数必须是size_t,其余参数以new所指定的placement arguments为初值.出现于new(...)小括号内的便是所谓的placement arguments.

只有当new所调用的ctor跑出exception,才会调用这些重载版的operator delete().它只可能这样被调用,主要用来归还未能完全创建成功的object所占用的memory.


 




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值