C++ に仮想コンストラクタがないのはなぜですか?
23 に答える
馬の口から聞こえます。:)
Bjarne Stroustrup の C++ Style and Technique FAQから 仮想コンストラクターがないのはなぜですか?
仮想呼び出しは、部分的な情報が与えられたときに作業を完了するためのメカニズムです。特に、「仮想」を使用すると、オブジェクトの正確なタイプではなく、インターフェイスのみを知っている関数を呼び出すことができます。オブジェクトを作成するには、完全な情報が必要です。特に、作成したいものの正確なタイプを知る必要があります。したがって、「コンストラクターへの呼び出し」を仮想にすることはできません。
FAQ エントリでは、仮想コンストラクターを使用せずにこの目的を達成するためのコードを提供しています。
私が考えることができる2つの理由:
技術的な理由
オブジェクトは、コンストラクターが終了した後にのみ存在します。コンストラクターが仮想テーブルを使用してディスパッチされるためには、仮想テーブルへのポインターを持つ既存のオブジェクトが存在する必要がありますが、オブジェクトが存在する場合、仮想テーブルへのポインターはどのように存在できますかまだ存在しない?:)
論理的理由
ある程度ポリモーフィックな動作を宣言する場合は、virtual キーワードを使用します。しかし、コンストラクターにはポリモーフィックなものはありません。C++ でのコンストラクターの仕事は、単にオブジェクト データをメモリに配置することです。仮想テーブル (および一般的なポリモーフィズム) はすべて、ポリモーフィック データではなくポリモーフィックな動作に関するものであるため、仮想コンストラクターを宣言しても意味がありません。
要約: C++ 標準は、「仮想コンストラクター」の表記法と動作を指定することができます。これは、合理的に直感的で、コンパイラーがサポートするのにそれほど難しくありませんが、/を使用して機能が既にきれいに実装されている(下)?パイプラインにある他の多くの言語提案ほど有用ではありません。create()
clone()
討論
「仮想コンストラクター」メカニズムを仮定しましょう。
Base* p = new Derived(...);
Base* p2 = new p->Base(); // possible syntax???
上記では、最初の行でDerived
オブジェクトを構築するため、*p
の仮想ディスパッチ テーブルは、2 行目で使用する「仮想コンストラクタ」を合理的に提供できます。(このページの「オブジェクトはまだ存在しないため、仮想構築は不可能です」と述べている数十の回答は、構築されるオブジェクトに不必要に近視眼的に焦点を合わせています。)
2 行目は、別のオブジェクトnew p->Base()
の動的割り当てとデフォルトの構築を要求する表記法を前提としています。Derived
ノート:
コンパイラは、コンストラクターを呼び出す前にメモリ割り当てを調整する必要があります。コンストラクターは通常、自動(非公式に「スタック」) 割り当て、静的(グローバル/名前空間スコープおよびクラス/関数
static
オブジェクト用)、および使用時に動的(非公式に「ヒープ」) をnew
によって構築されるオブジェクトのサイズは、
p->Base()
一般にコンパイル時に知ることができないため、動的割り当てが理にかなっている唯一のアプローチです。- GCC の可変長配列拡張など、実行時に指定された量のメモリをスタックに割り当てることは可能ですが
alloca()
、かなりの非効率性と複雑さをもたらします (たとえば、 hereとhereそれぞれ) 。
- GCC の可変長配列拡張など、実行時に指定された量のメモリをスタックに割り当てることは可能ですが
動的割り当ての場合、後でメモリを取得できるようにポインタを返す必要があります。
delete
仮定された表記法は
new
、動的割り当てとポインターの結果の型を強調するために明示的にリストします。
コンパイラは次のことを行う必要があります。
Derived
暗黙的virtual
sizeof
な関数を呼び出すか、RTTI を介してそのような情報を入手することにより、必要なメモリ量を調べますoperator new(size_t)
メモリを割り当てる呼び出しDerived()
配置で呼び出しますnew
。
また
- 動的割り当てと構築を組み合わせた関数用に追加の vtable エントリを作成する
したがって、仮想コンストラクターを指定して実装することは不可能ではないように思われますが、100 万ドルの費用がかかる問題は、既存の C++ 言語機能を使用して可能なことよりもどのように優れているかということです...? 個人的には、以下のソリューションに勝る利点はありません。
clone() と create()
C++ FAQは、新しい動的に割り当てられたオブジェクトをデフォルト構成またはコピー構成するメソッドを含む「仮想コンストラクター」イディオムを文書化しています。virtual
create()
clone()
class Shape {
public:
virtual ~Shape() { } // A virtual destructor
virtual void draw() = 0; // A pure virtual function
virtual void move() = 0;
// ...
virtual Shape* clone() const = 0; // Uses the copy constructor
virtual Shape* create() const = 0; // Uses the default constructor
};
class Circle : public Shape {
public:
Circle* clone() const; // Covariant Return Types; see below
Circle* create() const; // Covariant Return Types; see below
// ...
};
Circle* Circle::clone() const { return new Circle(*this); }
Circle* Circle::create() const { return new Circle(); }
引数を受け入れるように変更またはオーバーロードすることもできcreate()
ますが、基本クラス/インターフェイスのvirtual
関数シグネチャと一致するには、オーバーライドへの引数は基本クラスのオーバーロードの 1 つと正確に一致する必要があります。これらの明示的なユーザー提供の機能を使用すると、ロギング、インストルメンテーション、メモリ割り当ての変更などを簡単に追加できます。
意味論的な理由は別として、オブジェクトが構築されるまで vtable は存在しないため、仮想指定は役に立たなくなります。
C++ の仮想関数はランタイム ポリモーフィズムの実装であり、関数のオーバーライドを行います。通常、このvirtual
キーワードは、動的な動作が必要な場合に C++ で使用されます。オブジェクトが存在する場合にのみ機能します。一方、コンストラクターはオブジェクトの作成に使用されます。コンストラクターは、オブジェクトの作成時に呼び出されます。
したがってvirtual
、 virtual キーワードの定義に従って、コンストラクターを as として作成する場合、使用する既存のオブジェクトが必要ですが、オブジェクトの作成にはコンストラクターが使用されるため、このケースは存在しません。したがって、コンストラクターを仮想として使用しないでください。
したがって、仮想コンストラクター コンパイラーを宣言しようとすると、エラーがスローされます。
コンストラクターを仮想として宣言することはできません
@stefan の回答で、許可されていない例と技術的な理由を見つけることができます。私によると、この質問に対する論理的な答えは次のとおりです。
virtual キーワードの主な用途は、基本クラス ポインターが指すオブジェクトの型がわからない場合に多態的な動作を有効にすることです。
ただし、これはより原始的な方法であると考えてください。仮想機能を使用するには、ポインターが必要になります。そして、ポインターには何が必要ですか? 指し示すオブジェクト!(プログラムが正しく実行されるケースを考慮)
したがって、ポインタがそのオブジェクトを正しく指すことができるように、基本的にメモリ内のどこかに既に存在するオブジェクトが必要です (メモリがどのように割り当てられたかは関係ありません。コンパイル時または実行時である可能性があります)。
さて、ポイントされるクラスのオブジェクトにメモリが割り当てられている瞬間についての状況を考えてみてください->そのコンストラクターはそのインスタンス自体で自動的に呼び出されます!
したがって、コンストラクターが仮想であることを実際に心配する必要がないことがわかります。なぜなら、ポリモーフィックな動作を使用したい場合はいつでも、コンストラクターが既に実行されており、オブジェクトを使用できる状態にしているからです!
仮想関数は、ポインター自体の型ではなく、ポインターが指すオブジェクトの型に基づいて関数を呼び出すために使用されます。ただし、コンストラクターは「呼び出されません」。オブジェクトの宣言時に一度だけ呼び出されます。そのため、コンストラクターを C++ で仮想化することはできません。
オブジェクトタイプはオブジェクト作成の前提条件であるため、仮想コンストラクターの概念はうまく適合しませんが、完全に無視されているわけではありません。
GOF の「ファクトリ メソッド」設計パターンは、特定の設計状況で便利な仮想コンストラクタの「概念」を利用します。
このような質問を受けると、「これが実際に可能だったらどうなるだろうか」と考えるのが好きです。これが何を意味するのかはよくわかりませんが、作成されるオブジェクトの動的な型に基づいてコンストラクターの実装をオーバーライドできることに関係があると思います。
これには多くの潜在的な問題があります。1 つには、仮想コンストラクターが呼び出された時点で派生クラスが完全に構築されていないため、実装に問題が生じる可能性があります。
第二に、多重継承の場合はどうなるでしょうか? 仮想コンストラクターはおそらく複数回呼び出されるため、どのコンストラクターが呼び出されたかを知る方法が必要になります。
第 3 に、一般的に言えば、オブジェクトは構築時に完全に構築された仮想テーブルを持っていません。これは、オブジェクトの動的な型が構築時に認識されるという事実を考慮して、言語仕様に大幅な変更が必要になることを意味します。時間。これにより、基本クラスのコンストラクターは、完全に構築されていない動的クラス型を使用して、構築時に他の仮想関数を呼び出すことができます。
最後に、他の誰かが指摘したように、基本的に仮想コンストラクターと同じことを行う静的な「作成」または「初期化」タイプの関数を使用して、一種の仮想コンストラクターを実装できます。
仮想テーブル (vtable) は、1 つ以上の「仮想関数」を持つクラスごとに作成されます。そのようなクラスのオブジェクトが作成されるたびに、対応する vtable のベースを指す「仮想ポインタ」が含まれます。仮想関数呼び出しがある場合は常に、vtable を使用して関数アドレスに解決されます。クラスのコンストラクターが実行されると、メモリ内に vtable がないため、コンストラクターを仮想にすることはできません。これは、仮想ポインターがまだ定義されていないことを意味します。したがって、コンストラクターは常に非仮想でなければなりません。
C++ 仮想コンストラクターは使用できません。たとえば、コンストラクターを仮想としてマークすることはできません。このコードを試してください
#include<iostream.h>
using namespace std;
class aClass
{
public:
virtual aClass()
{
}
};
int main()
{
aClass a;
}
エラーが発生します。このコードは、コンストラクターを仮想として宣言しようとしています。ここで、仮想キーワードを使用する理由を理解してみましょう。Virtual キーワードは、ランタイム ポリモーフィズムを提供するために使用されます。たとえば、このコードを試してください。
#include<iostream.h>
using namespace std;
class aClass
{
public:
aClass()
{
cout<<"aClass contructor\n";
}
~aClass()
{
cout<<"aClass destructor\n";
}
};
class anotherClass:public aClass
{
public:
anotherClass()
{
cout<<"anotherClass Constructor\n";
}
~anotherClass()
{
cout<<"anotherClass destructor\n";
}
};
int main()
{
aClass* a;
a=new anotherClass;
delete a;
getchar();
}
In mainは、 の型として宣言された in のポインターにa=new anotherClass;
メモリを割り当てます。これにより、両方のコンストラクター ( Inと) が自動的に呼び出されます。したがって、コンストラクターを仮想としてマークする必要はありません。作成 (つまり、最初に基本クラス、次に派生クラス)。しかし、a を削除しようとすると、基本デストラクタのみが呼び出されます。そのため、仮想キーワードを使用してデストラクタを処理する必要があります。したがって、仮想コンストラクタは使用できませんが、仮想デストラクタは.ThanksanotherClass
a
aClass
aClass
anotherClass
delete a;
仮想メカニズムは、派生クラスオブジェクトへのベースクラスポインタがある場合にのみ機能します。構築には、基本クラスコンストラクター、基本的には派生クラスの基本クラスを呼び出すための独自のルールがあります。仮想コンストラクターはどのように役立つか、呼び出されるでしょうか?他の言語が何をするのかはわかりませんが、仮想コンストラクターがどのように役立つか、あるいは実装されるかはわかりません。仮想メカニズムが意味をなすように構築が行われている必要があり、多態的な動作のメカニズムを提供するvtable構造が作成されているためにも構築が行われている必要があります。
Vpointer は、オブジェクトの作成時に作成されます。vpointer は、オブジェクトの作成前には存在しません。したがって、コンストラクターを仮想として作成する意味はありません。