C++に仮想コンストラクタがないのはなぜですか?
馬の口から聞いてください:)。
Bjarne StroustrupのC++スタイルとテクニックからFAQ なぜ仮想コンストラクタがないのですか?
仮想呼び出しは、部分的な情報を指定して作業を完了するためのメカニズムです。特に、「仮想」では、オブジェクトの正確なタイプではなく、インターフェイスのみを知っている関数を呼び出すことができます。オブジェクトを作成するには、完全な情報が必要です。特に、作成するものの正確なタイプを知る必要があります。その結果、「コンストラクターの呼び出し」を仮想にすることはできません。
FAQエントリは、仮想コンストラクタなしでこの目的を達成する方法のコードを提供し続けます。
仮想関数は、基本的に多態的な動作を提供します。つまり、動的型が参照される静的(コンパイル時)型とは異なる動的型のオブジェクトを操作する場合、オブジェクトの代わりにactual型に適した動作を提供しますオブジェクトの静的タイプ。
次に、そのような動作をコンストラクターに適用してみます。オブジェクトを構築するとき、静的型は常に実際のオブジェクト型と同じです:
オブジェクトを構築するには、コンストラクターは作成するオブジェクトの正確なタイプを必要とします[...]さらに[...]コンストラクターへのポインターを持つことはできません
(Bjarne Stroustup(P424 The C++ Programming Language SE))
SmalltalkやPythonなどのオブジェクト指向言語とは異なり、コンストラクターはクラスを表すオブジェクトの仮想メソッドです(つまり、オブジェクトを渡すことができるため、GoF abstract factory pattern は不要です)独自に作成する代わりに周りのクラスを表す)、C++はクラスベースの言語であり、言語の構造を表すオブジェクトはありません。クラスは実行時にオブジェクトとして存在しないため、仮想メソッドを呼び出すことはできません。
これは「使用しないものにお金を払わない」という哲学に合致しますが、私が見たすべての大規模なC++プロジェクトは、何らかの形の抽象的なファクトリーまたはリフレクションを実装することになりました。
私が考えることができる2つの理由:
技術的な理由
オブジェクトは、コンストラクターが終了した後にのみ存在します。仮想テーブルを使用してコンストラクターをディスパッチするには、仮想テーブルへのポインターを持つ既存のオブジェクトが存在する必要がありますが、オブジェクトがまだ存在しない? :)
論理的理由
やや多形的な振る舞いを宣言する場合は、仮想キーワードを使用します。しかし、コンストラクタに多態性はありません。C++のコンストラクタの仕事は、メモリにオブジェクトデータを単に置くことです。仮想テーブル(および一般的なポリモーフィズム)は、すべてポリモーフィックなデータではなく、ポリモーフィックな振る舞いに関するものなので、仮想コンストラクタを宣言しても意味がありません。
意味上の理由は別として、オブジェクトが構築されるまでvtableは存在しないため、仮想的な指定は役に立たなくなります。
コンストラクタではありません:-)
struct A {
virtual ~A() {}
virtual A * Clone() { return new A; }
};
struct B : public A {
virtual A * Clone() { return new B; }
};
int main() {
A * a1 = new B;
A * a2 = a1->Clone(); // virtual construction
delete a2;
delete a1;
}
Summary:C++標準could「仮想コンストラクタ」の表記法と動作を指定します。これは、合理的で直感的であり、コンパイラーがサポートするのは難しくありませんが、特にfunctionalityすでにcreate()
/clone()
を使用してきれいに実装できますか(以下を参照)?パイプラインの他の多くの言語提案ほど有用ではありません。
「仮想コンストラクタ」メカニズムを仮定しましょう:
Base* p = new Derived(...);
Base* p2 = new p->Base(); // possible syntax???
上記では、最初の行はDerived
オブジェクトを構成するため、*p
の仮想ディスパッチテーブルは、2番目の行で使用するための「仮想コンストラクター」を合理的に提供できます。 (このページでは、"オブジェクトがまだ存在しないため、仮想構築が不可能です"と答える多数の回答があります。は、構築されるオブジェクトに不必要に近視的に焦点を当てています。)
2行目は、表記new p->Base()
を仮定して、別のDerived
オブジェクトの動的割り当てとデフォルトの構築を要求します。
ノート:
コンパイラはコンストラクタを呼び出す前にメモリ割り当てを調整する必要がある-コンストラクタは通常automatic(非公式の「スタック」)割り当て、static(グローバル/ネームスペーススコープおよびクラス// function -static
オブジェクト用)、およびdynamic(非公式には「ヒープ」)new
が使用される場合
p->Base()
によって構築されるオブジェクトのサイズは、一般にコンパイル時に知ることができないため、動的割り当てが唯一の意味のあるアプローチです
alloca()
-ただし、著しい非効率性と複雑さをもたらします(たとえば、それぞれ here および here )動的割り当ての場合、mustは、後でdelete
dがメモリに格納できるようにポインタを返します。
仮定表記では、new
を明示的にリストして、動的割り当てとポインター結果タイプを強調しています。
コンパイラは以下を行う必要があります。
Derived
virtual
関数を呼び出すか、そのような情報をRTTI経由で利用可能にすることにより、必要なメモリ量sizeof
を調べるoperator new(size_t)
を呼び出してメモリを割り当てますnew
でDerived()
を呼び出します。OR
だから-仮想コンストラクタを指定して実装するのは乗り越えられないように見えませんが、100万ドルの質問は次のとおりです:既存のC++言語機能を使用して可能なことよりも優れているでしょうか...?個人的には、以下のソリューションに比べてメリットはありません。
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つと完全に一致する必要があります。ユーザーが明示的に提供するこれらの機能により、ロギング、インスツルメンテーションの追加、メモリ割り当ての変更などが簡単になります。
オブジェクトタイプはオブジェクト作成の前提条件であるため、仮想コンストラクターの概念はうまく適合しませんが、完全に優先されるわけではありません。
GOFの「ファクトリーメソッド」デザインパターンは、仮想コンストラクターの「コンセプト」を利用します。これは、特定のデザイン状況では手軽です。
C++の仮想関数は、ランタイムポリモーフィズムの実装であり、関数のオーバーライドを行います。通常、動的な動作が必要な場合、C++ではvirtual
キーワードが使用されます。オブジェクトが存在する場合にのみ機能します。一方、コンストラクターはオブジェクトの作成に使用されます。コンストラクターは、オブジェクトの作成時に呼び出されます。
そのため、仮想キーワード定義に従ってコンストラクタをvirtual
として作成する場合、使用する既存のオブジェクトが必要ですが、コンストラクタを使用してオブジェクトを作成するため、このケースは存在しません。したがって、コンストラクターを仮想として使用しないでください。
したがって、仮想コンストラクタコンパイラを宣言しようとすると、エラーがスローされます。
コンストラクターは仮想として宣言できません
@stefanの回答では、例とそれが許可されない技術的な理由を見つけることができます。私によると、この質問に対する論理的な答えは次のとおりです。
仮想キーワードの主な用途は、基本クラスポインターが指すオブジェクトのタイプがわからない場合に多態的な動作を有効にすることです。
しかし、これはより原始的な方法であると考えてください。仮想機能を使用するには、ポインターが必要です。ポインターには何が必要ですか?指すオブジェクト! (プログラムの正しい実行の場合を考慮)
したがって、基本的には、ポインターがそのオブジェクトを正しく指すことができるように、メモリ内のどこかに既に存在するオブジェクトが必要です(メモリの割り当て方法に関係なく、コンパイル時または実行時のどちらでもかまいません)。
さて、ポイントされるクラスのオブジェクトにメモリが割り当てられる瞬間の状況を考えてみてください->そのコンストラクターはそのインスタンス自体で自動的に呼び出されます!
したがって、多態的な動作を使用する場合は、コンストラクターが既に実行されているため、コンストラクターが仮想化されていることを実際に心配する必要はありません!
仮想関数は、ポインター自体のタイプではなく、ポインターが指すオブジェクトのタイプに基づいて関数を呼び出すために使用されます。ただし、コンストラクターは「呼び出されません」。オブジェクトが宣言されたときに一度だけ呼び出されます。そのため、C++ではコンストラクターを仮想化できません。
Virtual-table(vtable)は、1つ以上の「仮想関数」を持つ各クラスに対して作成されます。そのようなクラスのオブジェクトが作成されるたびに、対応するvtableのベースを指す「仮想ポインター」が含まれます。仮想関数呼び出しがあるたびに、vtableを使用して関数アドレスに解決されます。クラスのコンストラクターが実行されるとき、メモリ内にvtableがないため、コンストラクターは仮想にできません。これは、仮想ポインターがまだ定義されていないことを意味します。したがって、コンストラクタは常に非仮想でなければなりません。
コンストラクター内でも仮想関数を呼び出さないでください。参照: http://www.artima.com/cppsource/nevercall.html
さらに、仮想コンストラクタが本当に必要かどうかもわかりません。それなしでポリモーフィックな構築を実現できます。必要なパラメーターに従ってオブジェクトを構築する関数を作成できます。
人々がこのような質問をするとき、私は自分自身に「これが実際に可能だったらどうなるのか」と考えたいです。私はこれが何を意味するのか本当にわかりませんが、作成されるオブジェクトの動的な型に基づいてコンストラクター実装をオーバーライドできることに関係があると思います。
これには多くの潜在的な問題があります。一つには、仮想コンストラクターが呼び出された時点で派生クラスが完全に構築されないため、実装に潜在的な問題があります。
第二に、多重継承の場合はどうなりますか?仮想コンストラクターが複数回呼び出されるとしたら、どのコンストラクターが呼び出されたかを知る何らかの方法が必要になります。
第三に、一般的に構築時に言えば、オブジェクトは完全に構築された仮想テーブルを持っていません。これは、オブジェクトの動的な型が構築時に知られるという事実を可能にするために言語仕様を大幅に変更する必要があることを意味します時間。これにより、基本クラスコンストラクターは、構築時に動的クラスタイプが完全に構築されていない状態で、他の仮想関数を呼び出すことができます。
最後に、他の誰かが指摘したように、基本的に仮想コンストラクターが行うのと同じことを行う静的な「作成」または「初期化」型関数を使用して、一種の仮想コンストラクターを実装できます。
仮想メカニズムは、派生クラスオブジェクトへのベースクラスポインターがある場合にのみ機能します。コンストラクションには、基本クラスから派生クラスへの基本クラスコンストラクターの呼び出しに関する独自のルールがあります。仮想コンストラクタはどのように役立つか、呼び出されますか?他の言語が何をするのかは知りませんが、仮想コンストラクターがどのように役立つか、さらには実装されるかはわかりません。仮想機構が意味をなすために構築が行われる必要があり、また、多形挙動のメカニズムを提供するvtable構造が作成されるために構築が行われる必要があります。
私たちは単にそれを好きだと言ってください。コンストラクタを継承することはできません。したがって、仮想は多態性を提供するため、それらを仮想と宣言する意味はありません。
C++仮想コンストラクターは使用できません。たとえば、コンストラクターを仮想としてマークすることはできません。このコードを試してください。
#include<iostream.h>
using namespace std;
class aClass
{
public:
virtual aClass()
{
}
};
int main()
{
aClass a;
}
エラーが発生します。このコードは、コンストラクターを仮想として宣言しようとしています。次に、仮想キーワードを使用する理由を理解してみましょう。仮想キーワードは、ランタイムポリモーフィズムを提供するために使用されます。たとえば、このコードを試してください。
#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();
}
メインのa=new anotherClass;
は、anotherClass
の型として宣言されたポインターa
のaClass
にメモリを割り当てます。これにより、両方のコンストラクター(aClass
およびanotherClass
)が自動的に呼び出されます。作成の連鎖に従う必要があります(つまり、最初に基本クラス、次に派生クラス)。しかし、delete a;
を削除しようとすると、ベースデストラクタのみが呼び出されるため、仮想キーワードを使用してデストラクタを処理する必要があります。 つまり、仮想コンストラクタは不可能ですが、仮想デストラクタは可能です。ありがとう
非常に基本的な理由があります:コンストラクターは事実上静的関数であり、C++では静的関数は仮想化できません。
C++の経験が豊富な場合は、静的関数とメンバー関数の違いをすべて知っています。静的関数は、オブジェクト(インスタンス)ではなくCLASSに関連付けられているため、「this」ポインターは表示されません。メンバーテーブルのみが仮想になります。vtable(「仮想」を機能させる関数ポインターの隠しテーブル)は、実際には各オブジェクトのデータメンバーであるためです。
さて、コンストラクターの仕事は何ですか?名前にあります-"T"コンストラクターは、Tオブジェクトが割り当てられるとそれを初期化します。これにより、自動的にメンバー関数になりません!オブジェクトは、「this」ポインター、つまりvtableを取得する前に存在する必要があります。つまり、言語がコンストラクターを通常の関数として扱ったとしても(関連する理由から私は入りません)、コンストラクターは静的メンバー関数である必要があります。
これを見る素晴らしい方法は、「ファクトリー」パターン、特にファクトリー関数を見ることです。彼らはあなたが望んでいることをします。クラスTにファクトリメソッドがある場合、それは常に静的であることに気付くでしょう。そうでなければなりません。
C++でコンストラクターがどのように機能し、仮想関数の意味/使用法が何であるかを論理的に考えると、C++では仮想コンストラクターが無意味であることがわかります。 C++で仮想のものを宣言するということは、現在のクラスのサブクラスでオーバーライドできることを意味しますが、オブジェクトの作成時にコンストラクターが呼び出されますが、その時点でクラスのサブクラスを作成することはできません。クラスを作成して、コンストラクター仮想を宣言する必要がないようにします。
また、別の理由として、コンストラクターはクラス名と同じ名前を持ち、コンストラクターを仮想として宣言する場合は、派生クラスで同じ名前で再定義する必要がありますが、2つのクラスの同じ名前を持つことはできません。そのため、仮想コンストラクタを持つことはできません。
コンストラクターが呼び出されると、その時点まで作成されたオブジェクトはありませんが、オブジェクトが属するクラスの特定のコンストラクターが既にあるため、作成されるオブジェクトの種類はまだわかっています呼ばれた。
関数に関連付けられたVirtual
キーワードは、特定のオブジェクトタイプの関数が呼び出されることを意味します。
だから、私の考えでは、オブジェクトを作成する目的のコンストラクタが既に呼び出されているため、仮想コンストラクタを作成する必要はないと言います。コンストラクタを仮想にすることは、オブジェクト固有のコンストラクタはすでに呼び出されており、これはクラス固有の関数の呼び出しと同じです。これは、仮想キーワードによって実現されます。
内部実装では、vptrおよびvtableに関連する理由で仮想コンストラクターが許可されません。
もう1つの理由は、C++が静的に型付けされた言語であり、コンパイル時に変数の型を知る必要があることです。
コンパイラーは、オブジェクトを作成するためにクラス型を認識する必要があります。作成するオブジェクトのタイプは、コンパイル時の決定です。
コンストラクタを仮想化すると、コンパイル時にオブジェクトの型を知る必要がないことを意味します(仮想関数が提供するものです。実際のオブジェクトを知る必要はなく、実際のオブジェクトをポイントすると、オブジェクトのタイプがわからないままポイントされたオブジェクトの仮想関数を呼び出します)、コンパイル時にオブジェクトのタイプがわからない場合は、静的に型付けされた言語に反します。したがって、実行時のポリモーフィズムは実現できません。
したがって、コンストラクタは、コンパイル時にオブジェクトのタイプを知らなくても呼び出されません。したがって、仮想コンストラクタを作成するというアイデアは失敗します。