14

コンストラクターからの呼び出し仮想関数と純粋仮想関数の重複ではありません:

以前の質問は、C++ 11 の新しいコンストラクター委任の動作ではなく、C++ 03 に関連しており、純粋な仮想実装が実行される前に、委任を使用して適切な構築を保証することにより、未定義の動作の軽減に対処していません。

C++ 11 では、構築中にクラスのコンストラクターで Pure Virtual 関数を呼び出すことの危険性は何ですか?

どうやら、C++ 11 仕様のどこかにそのような制約が存在し、

メンバー関数 (仮想メンバー関数、10.3 を含む) は、構築中のオブジェクトに対して呼び出すことができます。同様に、構築中のオブジェクトは typeid 演算子のオペランドになることができます.. papers/2011/n3242.pdf ) 公開仕様の「フェアユース」バージョンが見つかりません。

C++11 では、コンストラクターの実行が終了すると、オブジェクトが構築されたと見なされます。複数のコンストラクターの実行が許可されるため、これは、各デリゲート コンストラクターが独自の型の完全に構築されたオブジェクトで実行されることを意味します。派生クラス コンストラクターは、基本クラスの委任がすべて完了した後に実行されます。-ウィキペディアによると、これは C++ 11 のものです。

実際の C++ 11 リファレンスは不明です。

次の例は、Visual Studio 2012 C++ コンパイラの Nov CTP でコンパイルして実行します。

#include <string>

/**************************************/
class Base
{
public:
    int sum;
    virtual int Do() = 0;

    void Initialize()
    {
        Do();
    }
    Base()
    {
    }
};

/**************************************/
// Optionally declare class as "final" to avoid
// issues with further sub-derivations.
class Derived final : public Base
{
public:

    virtual int Do() override final
    {
        sum = 0 ? 1 : sum;
        return sum / 2 ; // .5 if not already set.
    }

    Derived(const std::string & test)
        : Derived() // Ensure "this" object is constructed.
    {
        Initialize(); // Call Pure Virtual Method.
    }
    Derived()
        : Base()
    {
        // Effectively Instantiating the Base Class.
        // Then Instantiating This.
        // The the target constructor completes.
    }
};




/********************************************************************/
int main(int args, char* argv[])
{
    Derived d;
    return 0;
}
4

2 に答える 2

8

更新により、コード例は私には問題ないように見えますが、Derived のサブクラスを作成した場合、サブクラスの Do() のオーバーライドは Derived(const std::string &) ではなく Derived によって呼び出されません。 ::Do() は引き続き呼び出されます。これはあなたが望んでいたものではないかもしれません。特に、Initialize() が Derived(const std::string &) コンストラクターから呼び出された場合、オブジェクトは引き続き Derived オブジェクトの「のみ」であり、SubDerived オブジェクトではありません (construction-code の SubDerived レイヤーがSubDerived::Do() ではなく、Derived::Do() が呼び出されるのはそのためです。

Q: サブクラスが同じ委任パターンを使用して、すべてが同じ方法でインスタンス化されるようにするとどうなりますか?

A: それはほとんどうまくいきますが、SubDerived::Do() が呼び出される前に Derived::Do() が呼び出されても問題ない場合に限ります。

特に、上記の Derived と同じことを行うクラス SubDerived があるとします。次に、呼び出し元のコードがこれを行ったとき:

SubDerived foo("Hello");

次の一連の呼び出しが発生します。

Base()
Derived()
Derived(const std::string &)
  Base::Initialize()
    Derived::Do()
SubDerived()
SubDerived(const std::string &)
  Base::Initialize()
    SubDerived::Do()

...そうです、SubDerived::Do() は最終的に呼び出されますが、Derived::Do() も呼び出されます。それが問題になるかどうかは、さまざまな Do() メソッドが実際に何をするかによって異なります。

アドバイス: コンストラクター内から仮想メソッドを呼び出すことは、通常、最善の方法ではありません。オブジェクトが構築された後、オブジェクトに対して手動で Do() を呼び出す呼び出しコードを単純に要求することを検討することをお勧めします。呼び出しコードの作業が少し増えますが、利点は、部分的に構築されたオブジェクトで仮想メソッド呼び出しを行うときに発生する、あまり明白ではない、または便利ではないセマンティクスを回避できることです。

于 2013-02-04T07:18:14.533 に答える
5

典型的な単一コンストラクターの継承シナリオでは、基本コンストラクターで純粋仮想関数を呼び出すのは UB です。

[C++11: 10.4/6]:メンバ関数は、抽象クラスのコンストラクタ (またはデストラクタ) から呼び出すことができます。そのようなコンストラクタ (またはデストラクタ) から作成 (または破棄) されるオブジェクトに対して、直接的または間接的に純粋仮想関数への仮想呼び出し (10.3) を行うことの効果は定義されていません。

struct Base
{
   Base()
   {
      foo();  // UB
   }

   virtual void foo() = 0;
};

struct Derived : Base
{
   virtual void foo() {}
};

この時点では、オブジェクトのより派生した部分がまだ構築されていないため、デリゲートされたコンストラクター呼び出しで行われるそのような呼び出しの例外はありません。

struct Base
{
   Base()
   {
      foo();  // still UB
   }

   Base(int) : Base() {};

   virtual void foo() = 0;
};

struct Derived : Base
{
   virtual void foo() {}
};

あなたが引用したウィキペディアの一節は次のとおりです。

C++11 では、コンストラクターの実行が終了すると、オブジェクトが構築されたと見なされます。複数のコンストラクターの実行が許可されるため、これは、各デリゲート コンストラクターが独自の型の完全に構築されたオブジェクトで実行されることを意味します。派生クラス コンストラクターは、基本クラスの委任がすべて完了した後に実行されます。

ひと目見ただけで誤解されるかもしれないので、重要なのは最初の文ではなく、 2 番目の太字の文です。

ただし、投稿されたコード スニペットは問題ありません。これは、既に完全に構​​築されている抽象クラスのコンストラクターではなく、派生コンストラクター本体が実行されているためです。とはいえ、安全であるという証拠を求めなければならなかったという事実は、これが最も表現力や直感的なアプローチではないことを示すものであり、私はあなたの設計ではそれを避けようとします.

于 2013-02-04T07:17:50.913 に答える