1

Inside C++ Object Modelという本でオブジェクトの破壊に関連するトピックを読んでいるときに、この問題に遭遇しました。

ユーザー定義のデストラクタを実行する前に、デストラクタが拡張されることを示しています。vptr拡張の最初のステップは、そのクラスの仮想関数テーブルへのポインターをリセットすることです。それに応じて、コンストラクターでユーザー コード (ブロックされたコンストラクター本体のステートメント) を実行する直前に、構築中にvptr仮想メンバー関数を呼び出す必要がある場合に備えて、 が既に適切に設定されていることを覚えています。

問題は、デストラクタ拡張のリセットvptr手順が必須かどうかです。vptrもしそうなら、オブジェクト内の がどこかで更新される可能性があるはずです。これはいつ起こりますか?

4

2 に答える 2

3

派生クラスのデストラクタで発生する可能性があります。あなたが持っているとしましょう:

class Foo : public Bar : public Baz

今、あなたが持っているとしましょうFoo。ではFoo::~Fooであり、Fooそれが使用する必要がある仮想関数テーブルです。しかし、Foo::~Foo完了すると、それはもうありFooません。それはBarであり、それが使用しなければならない仮想関数テーブルです。がBar::~Bar完了すると、それは単なる であるBazため、Baz::~Bazでは、 の仮想関数テーブルを使用する必要がありますBaz

仮想関数テーブルへのポインターは、コンストラクターとデストラクター以外では変更されません。

遊ぶためのコード例を次に示します。

    #include <string>  
    #include <iostream>

    class Foo
    {
    public:
        Foo() { print("Foo::Foo"); }
        virtual ~Foo() { print("Foo::~Foo"); }
        virtual void print(std::string j) { std::cout << j << "(Foo)" << std::endl; }
    };

    class Bar : public Foo
    {
    public:
        Bar() { print("Bar::Bar"); }
        virtual ~Bar() { print("Bar::~Bar"); }
        virtual void print(std::string j) { std::cout << j << "(Bar)" << std::endl; }
    };

    class Baz : public Bar
    {
    public:
        Baz() { print("Baz:Baz"); }
        virtual ~Baz() { print("Baz::~Baz"); }
        virtual void print(std::string j) { std::cout << j << "(Baz)" << std::endl; }
    };

    int main(void)
    {
        std::cout << "Constructing Baz" << std::endl;
        {
            Baz j;
            std::cout << "Baz constructed" << std::endl;
        }
        std::cout << "Baz destructed" << std::endl;
    }

出力は次のとおりです。

Constructing Baz
Foo::Foo(Foo)
Bar::Bar(Bar)
Baz:Baz(Baz)
Baz constructed
Baz::~Baz(Baz)
Bar::~Bar(Bar)
Foo::~Foo(Foo)
Baz destructed

a がどのようにFoo構築されBar、最終的な を作成するために使用される a を作成するために使用されるかを見ることができますBaz。破壊時に に~Baz変えてからBar~Bar変えるFoo~Foo最終的な破壊を行います。

于 2013-10-06T05:14:21.633 に答える
1

いいえ、そのような可能性はありません。はvptr、コンストラクターおよびデストラクタからのみ更新されます。

デストラクタからの更新は、非常に具体的な理由で行われます。クラスのデストラクタ内から呼び出されるすべての仮想関数が、階層内でA定義されているAかそれより上位の階層で定義されている仮想関数を呼び出すが、下位階層にあるクラスからの関数は呼び出さないことを確認するためです。vptr基本的に、これは各コンストラクターでもポインターが更新されるのと同じ (対称的な) 理由です。

たとえば、この階層では

struct A {
  virtual void foo() { std::cout << "A" << std::endl; }
  ~A() { foo(); }
};

struct B : A {
  virtual void foo() { std::cout << "B" << std::endl; }
  ~B() { foo(); }
};

struct C : B {
  virtual void foo() { std::cout << "C" << std::endl; }
  ~C() { foo(); }
};

C c;

オブジェクトのデストラクタ チェーン内の各デストラクタは、c仮想関数の呼び出しを実行しますfooCwill callのデストラクタC::fooBwill call B::foo(not C::foo) のデストラクタ、およびAwill call A::foo(再び、 not C::foo) のデストラクタ。これは、各デストラクタvptrが独自のクラスの仮想テーブルへのポインタを明示的に設定するためです。

同じ動作のより複雑な例は、次のようになります。

struct A;
extern void (A::*fun)();

struct A {
  virtual void foo() { std::cout << "A" << std::endl; }
  ~A() { (this->*fun)(); }
};

void (A::*fun)() = &A::foo;

struct B : A {
  virtual void foo() { std::cout << "B" << std::endl; }
  ~B() { (this->*fun)(); }
};

struct C : B {
  virtual void foo() { std::cout << "C" << std::endl; }
  ~C() { (this->*fun)(); }
};

C c;

違いは、この例ではvptrおよび仮想メソッド テーブルを物理的に使用して呼び出しを解決する可能性が高いことです。前の例は通常、適切なへの直接の非仮想呼び出しにコンパイラによって最適化されますfoo

于 2013-10-06T05:19:54.550 に答える