1

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 です。最初の 3 つのケースは理解できましたが、最後のケースは理解できませんでした。最後と最初の違いは、仮想デストラクターです。これは関連していますか?はいの場合、どのように?

4

2 に答える 2

3

コード例には、実装定義の動作があります。いずれの場合の出力も保証できません。クラスのメンバーが常に連続したメモリ位置に配置されるとは限りません。それらの間にパディング バイトが追加される可能性があります。また、パディングを追加するかどうかは、実装の詳細として省略されています。virtual役を演じているというあなたの疑いは、本当かもしれません[注 1:]。ただし、注意すべき重要なことはvirtual、出力がなくても出力が保証されないことです。

参考:
C++11: 9.2 クラスメンバ [class.mem]

14) 同じアクセス制御 (第 11 節) を持つ (非共用体) クラスの非静的データ メンバーは、後のメンバーがクラス オブジェクト内でより高いアドレスを持つように割り当てられます。アクセス制御が異なる非静的データメンバーの割り当て順序は規定されていません (11)。実装のアライメント要件により、隣接する 2 つのメンバーが互いの直後に割り当てられない場合があります。仮想関数 (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
于 2013-01-19T13:19:24.807 に答える
1

はい、仮想メンバー関数 (この場合はデストラクタ) は「vtable」に保持され、多くの場合、クラス データ構造の最初の要素として格納されます。

ただし、C++ 標準には、これが当てはまるという厳格な規則がないことに注意してください。

于 2013-01-19T13:23:49.643 に答える