C++对象模型

本文基于《Inside the C++ object model》一书,探讨C++对象模型中的封装成本、类内存布局、默认构造函数、数据和函数的语义等主题。作者通过实例展示了如何查看类的内存布局,讨论了构造函数的执行顺序、继承下的构造与析构、虚函数与多态等概念,并提出了对C++新标准中移动构造与赋值构造的思考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这篇文章是我读了Inside the C++ object model这本书,结合自己的实际经验,对C++对象模型中一些困惑的点,写了一点感想。我想只有懂了设计者的意图,才能对这门语言有所领悟。即便是看了一遍书,还是有很多地方想亲手试验一下,以确认书中所说的和现在的编译器是否吻合。本文以书的章节为顺序,以举例的方式列出了我的一些思考

Layout Cost for adding Encapsulation

查看类的内存

本书中提到了对象的很多模型,而我确喜欢实际应用
自然对msvc和gcc所实现的类的内存模型比较关心。
那么能够直观的看到类的内存模型,自然是十分令人兴奋的事情了
查看类的布局能够对C++对象模型和编译器有更好的了解

  • msvc

msvc中就有很直观的方法来实现这一功能


打印所有类内存结构
cl /d1 reportAllCLassLayout
打印指定类内存结构,类名前面没有空格
cl /d1 reportSingleClassLayout[classname]


下面是一个例子
文件名:_test.cpp

class IBaseA
{
public:
    //virtual void fnA() = 0;
    int m_nTestA;
};

class IBaseB
{
public:
    virtual void fnB() = 0;
    int m_nTestB;
};

class CTest : public IBaseA,public IBaseB
{
public:
    //virtual void fnA(){ printf("fnA\n"); }
    virtual void fnB(){ printf("fnB\n"); }
};

int main()
{
    CTest *pTest = new CTest;
    void *p = (void*)pTest;
    IBaseA *pBaseA = (IBaseA*)p;
    //pBaseA->fnA();

    IBaseB *pBaseB = (IBaseB*)p;
    pBaseB->fnB();

    pBaseB = (IBaseB*)pTest;  //相当于做了指针的偏移
    pBaseB->fnB();

    return 0;
}

命令:cl /d1 reportSingleClassLayoutCTest _test.cpp

class CTest     size(12):   
        +---   
        | +--- (base class IBa   
 0      | | {vfptr}   
 4      | | m_nTestB   
        | +---   
        | +--- (base class IBa   
 8      | | m_nTestA   
        | +---   
        +---   

CTest::$vftable@:   
        | &CTest_meta   
        |  0   
 0      | &CTest::fnB   

CTest::fnB this adjustor: 0   
  • gcc

命令gcc -fdump-class-hierarchy _test.cpp
类的内存结构在生成的class文件里面

Class IBaseA
   size=4 align=4
   base size=4 base align=4
IBaseA (0x0x4cf1700) 0

Vtable for IBaseB
IBaseB::_ZTV6IBaseB: 3u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI6IBaseB)
8     (int (*)(...))__cxa_pure_virtual

Class IBaseB
   size=8 align=4
   base size=8 base align=4
IBaseB (0x0x4cf1738) 0
    vptr=((& IBaseB::_ZTV6IBaseB) + 8u)

Vtable for CTest
CTest::_ZTV5CTest: 3u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI5CTest)
8     (int (*)(...))CTest::fnB

Class CTest
   size=12 align=4
   base size=12 base align=4
CTest (0x0x6982180) 0
    vptr=((& CTest::_ZTV5CTest) + 8u)
  IBaseA (0x0x4cf1770) 8
  IBaseB (0x0x4cf17a8) 0
      primary-for CTest (0x0x6982180)

Default Constructor

编译器自动生成的这些函数只有在编译器需要的时候才生成,
如果编译结束都没有使用的函数,是不会被生成的

  • 构造函数的扩张

在构造函数内部优先调用数据成员的构造函数
TODO调用数据成员的构造函数的顺序,猜测和声明顺序一致

可以编译,不能链接
设计者必须定义pure virtual destructor

class Animal
{
public:
    virtual ~Animal()=0;
};

class Dog : public Animal
{};

int main()
{
    Dog d;
    return 0;
}
  • 继承情况下的构造和析构的顺序
#include "stdafx.h"
#include "iostream"
using namespace std;
class Base
{
public:
    Base(){ std::cout<<"Base::Base()"<<std::endl; }
    ~Base(){ std::cout<<"Base::~Base()"<<std::endl; }
};

class Base1:public Base
{
public:
    Base1(){ std::cout<<"Base1::Base1()"<<std::endl; }
    ~Base1(){ std::cout<<"Base1::~Base1()"<<std::endl; }
};

class Derive
{
public:
    Derive(){ std::cout<<"Derive::Derive()"<<std::endl; }
    ~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; }
};

class Derive1:public Base1
{
private:
    Derive m_derive;
public:
    Derive1(){ std::cout<<"Derive1::Derive1()"<<std::endl; }
    ~Derive1(){ std::cout<<"Derive1::~Derive1()"<<std::endl; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    Derive1 derive;
    return 0;
}

运行结果是:
Base::Base()
Base1::Base1()
Derive::Derive()
Derive1::Derive1()
Derive1::~Derive1()
Derive::~Derive()
Base1::~Base1()
Base::~Base()

这里构造和析构时的调用顺序是相反的
TODO这里是先调用父类构造函数,还是调用自身数据成员的构造函数

  • 当编译器无法生成默认构造函数的时候也会罢工

下面是一个例子

class Animal
{
public:
    int &height;
};

int main()
{
    Animal a;//note: 'Animal::Animal()' is implicitly deleted because the default definition would be ill-formed:
}
  • 虚基类构造函数
class Base 
{
public:
    Base(int m):b(m){}
    int b;
};

class A : virtual public Base
{
public:
    A():a(5),Base(99){}
    int a;
};

class B : virtual public Base
{
public:
    B():b(6),Base(100){}
    int b;
};

class Derive:public A, public B
{
public:
    Derive(int q):B(),A(),Base(15){}    //虚基类必须这样,非虚基类不能隔代
};

int main()
{
    Derive d(12);
    return 0;
}
  • C++新标准的move construct & move assignment construct

TODO

the semantics of data

  • static 和 const
class Animal
{
public:
    static void eat() const;
};

const 成员函数是包含this指针的 。这明显不被static函数允许

  • 继承和多态

继承的注意事项
在private继承中,子类public不能改变private的属性

class Animal
{
public:
    virtual void bark();
};

class Dog : private Animal
{
public:     //it should be private
    void bark()
    {
        cout<<"wang"<<endl;
    }
};

int main()
{
    Dog d0;
    Dog *d1;
    //_test.obj : error LNK2001: 无法解析的外部符号 "public: virtual void __thiscall Animal::bark(void)" (?bark@Animal@@UAEXXZ)
    //d0.bark();

    //编译通过,运行错误
    //d1->bark();

    //'Animal' is an inaccessible base of 'Dog',private不允许这样转换
    Animal *a = new Dog();
    a->bark();
}

多态依赖于虚拟成员函数,只有指针才能实现多态
实现细节是在指针查找虚表寻址方式的时候发生的

  • TODO MFC和Qt避免继承负担的封装措施

the semantics of function

  • 虚拟成员函数

转换成虚表寻址方式,也是这种方式实现了多态

  • const成员函数

在c++中,非const对象是可以调用const成员函数的

#include<iostream>
using namespace std;

class Animal
{
public:
    int height;
    void bark() //const对象只能调用const成员函数
    {
        cout<<"wang"<<endl;
    }
};

void test(const Animal a)
{
    a.bark();
}

int main()
{
    Animal a;
    test(a);
    return 0;
}
  • 通过指针访问private成员函数
class Dog
{
private:
    int numbers;
    void setnum(int n)
    {
        numbers=n;
        cout<<numbers<<endl;
    }
public:
    Dog(){}
    Dog(string str,int n)
    {
        numbers = n;
    }
    int getnum()
    {
        cout<<numbers<<endl;
        return numbers;
    }
};

int main()
{
    Dog d;
    *(int *)&d = 5555;
    void (Dog::*pf)(int);
    void *p;
    Dog *D = &d;
    pf = &D->setnum;
    (D->*pf)(78);
}

指向成员函数的指针,加上域操作符表示含有this指针

void (Animal::*pf)()
pf = &Animal::eat()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值