c++类及其布局和转换

c++ Classes and their layouts and conversions

本文关键字:转换 布局 c++      更新时间:2023-10-16

我得到了这个代码来测试我的cpp理解,我很困惑:

#include "stdafx.h"
#include <iostream>
#include <cstddef>
using namespace std;
class A
{
public:
    A() : m_x(0) { }
public:
    static ptrdiff_t member_offset(const A &a)
    {
        const char *p = reinterpret_cast<const char*>(&a);
        const char *q = reinterpret_cast<const char*>(&a.m_x);
        return q - p;
    }
private:
    int m_x;
};
class B
    : public A
{
public:
    B() : m_x('a') { }
public:
    static int m_n;
public:
    static ptrdiff_t member_offset(const B &b)
    {
        const char *p = reinterpret_cast<const char*>(&b);
        const char *q = reinterpret_cast<const char*>(&b.m_x);
        return q - p;
    }
private:
    char m_x;
};
int B::m_n = 1;
class C
{
public:
    C() : m_x(0) { }
    virtual ~C() { }
public:
    static ptrdiff_t member_offset(const C &c)
    {
        const char *p = reinterpret_cast<const char*>(&c);
        const char *q = reinterpret_cast<const char*>(&c.m_x);
        return q - p;
    }
private:
    int m_x;
};
int _tmain(int argc, _TCHAR* argv[])
{
    A a;
    B b;
    C c;
    std::cout << ((A::member_offset(a) == 0) ? 0 : 1);
    std::cout << ((B::member_offset(b) == 0) ? 0 : 2);
    std::cout << ((A::member_offset(b) == 0) ? 0 : 3);
    std::cout << ((C::member_offset(c) == 0) ? 0 : 4);
    std::cout << std::endl;
    return 0;
}

答案是0204。前三个案例我懂了,但最后一个我不懂。最后一个和第一个的区别是一个虚析构函数。这有关系吗?如果是,怎么做?

代码示例有一个实现定义的行为。不能保证任何情况下的输出。而不是保证类的成员总是被放置在连续的内存位置。它们之间可以添加填充字节。是否添加填充作为实现细节被忽略。你对virtual起作用的怀疑可能是真的[注1]。但重要的是要注意的是,即使没有virtual,输出也不能保证。

参考:


c++ 11.9.2类成员[Class .mem]

14)具有相同访问控制(第11条)的(非联合)类的非静态数据成员是这样分配的以后的成员在类对象中拥有更高的地址。非静态数据的分配顺序具有不同访问控制的成员未指定(11)。实现对齐要求可能导致两个相邻的成员不能在彼此之后立即分配;所以对管理虚函数(10.3)和虚基类(10.1)的空间。


<一口>[注1]:
动态调度本身是一种实现定义的机制,但是大多数(读所有已知的)实现使用虚表和指针机制来实现它。对于多态类(不从任何其他类派生),通常虚指针存储为类的第一个元素。因此,可以合理地假设,在最后一种情况下,当您在环境中运行代码示例时,这就是在幕后发生的事情。

在线样本:

#include<iostream>
using std::cout;
using std::endl;
class B;
typedef void (*HANDLE_DOSOMETHING)(B *const, int q);
class B
{
public:
  virtual void doSomething(int q)
  {
      std::cout<<"B::doSomething()"<<q<<endl;
  }
  void dummy()
  {
      HANDLE_DOSOMETHING *f1ptr = NULL;
      int                *vtbl  = NULL;
      int                *vptr  = (int *)this; // address of the object
      vtbl = (int *)*vptr; //address of the VTABLE
      f1ptr = (HANDLE_DOSOMETHING *)&(vtbl[0]); //address of the 1st virtual function
      (*f1ptr)(this, 55);
   }
};
int main()
{
    B objb;
    objb.dummy();
    return 0;  
}
输出:

B::doSomething()55

是的,虚成员函数(在本例中是析构函数)保存在虚函数表中,虚函数表通常作为类数据结构的第一个元素存储。

但是,请注意c++标准中没有严格的规则规定这种情况