web-dev-qa-db-ja.com

C ++でコンストラクターを明示的に呼び出す理由

スコープ解決演算子、つまりclassName::className()を使用して、C++でクラスのコンストラクターを明示的に呼び出すことができることを知っています。私はそのような電話をどこにかける必要があるのか​​正確に考えていました。

21
Arnkrishn

ほとんどの場合、いくつかのパラメーターを必要とする子クラスコンストラクターでは:

class BaseClass
{
public:
    BaseClass( const std::string& name ) : m_name( name ) { }

    const std::string& getName() const { return m_name; }

private:

    const std::string m_name;

//...

};


class DerivedClass : public BaseClass
{
public:

    DerivedClass( const std::string& name ) : BaseClass( name ) { }

// ...
};

class TestClass : 
{
public:
    TestClass( int testValue ); //...
};

class UniqueTestClass 
     : public BaseClass
     , public TestClass
{
public:
    UniqueTestClass() 
       : BaseClass( "UniqueTest" ) 
       , TestClass( 42 )
    { }

// ...
};

... 例えば。

それ以外には、ユーティリティは表示されません。私が若すぎて自分が本当に何をしているのかを知ることができなかったときだけ、私は他のコードでコンストラクターを呼び出しました...

14
Klaim

また、コンストラクタを明示的に使用して一時ファイルを作成することもあります。たとえば、コンストラクタを持つクラスがある場合:

class Foo
{
    Foo(char* c, int i);
};

そして機能

void Bar(Foo foo);

しかし、あなたはフーを持っていません、あなたはそうすることができます

Bar(Foo("hello", 5));

これはキャストのようなものです。実際、パラメーターを1つしか取らないコンストラクターがある場合、C++コンパイラーはそのコンストラクターを使用して暗黙的なキャストを実行します。

既存のオブジェクトでコンストラクターを呼び出すことはnotで正当です。つまり、あなたはできません

Foo foo;
foo.Foo();  // compile error!

あなたが何をしても関係ありません。しかし、メモリを割り当てずにコンストラクタを呼び出すことができます。それがplacement newの目的です。

char buffer[sizeof(Foo)];      // a bit of memory
Foo* foo = new(buffer) Foo();  // construct a Foo inside buffer

新しいメモリを割り当て、新しいメモリを割り当てる代わりに、その場所にオブジェクトを構築します。この使用法は悪と見なされ、ほとんどのタイプのコードではまれですが、埋め込みコードやデータ構造コードでは一般的です。

例えば、 std::vector::Push_backはこの手法を使用して、コピーコンストラクターを呼び出します。これにより、空のオブジェクトを作成して代入演算子を使用するのではなく、コピーを1回実行するだけで済みます。

44
Charlie

コンパイラーエラーC2585のエラーメッセージは、コンストラクターでスコープ解決演算子を実際に使用する必要がある最も良い理由であると思います。これは、チャーリーの答えと関連しています。

多重継承に基づくクラスまたは構造タイプからの変換。タイプが同じ基本クラスを2回以上継承する場合、変換関数または演算子はスコープ解決(::)を使用して、使用する継承クラスのどれを指定する必要があります変換中

したがって、BaseClassがあり、BaseClassAとBaseClassBの両方がBaseClassを継承し、DerivedClassがBaseClassAとBaseClassBの両方を継承するとします。

変換または演算子のオーバーロードを実行してDerivedClassをBaseClassAまたはBaseClassBに変換する場合、変換で使用するコンストラクター(コピーコンストラクター、IIRCのようなものを考えています)を識別する必要があります。

3

一般に、コンストラクタを直接呼び出すことはありません。 new演算子がそれを呼び出すか、サブクラスが親クラスのコンストラクターを呼び出します。 C++では、基本クラスは、派生クラスのコンストラクターが開始する前に完全に構​​築されることが保証されています。

コンストラクタを直接呼び出すのは、newを使用せずにメモリを管理している非常にまれなケースだけです。そしてそれでも、あなたはそれをすべきではありません。代わりに、演算子newの配置フォームを使用する必要があります。

2
jmucchiello

クラスコンストラクターを公開する有効なユースケースがあります。たとえば、アリーナアロケーターを使用して独自のメモリ管理を行う場合は、割り当てとオブジェクトの初期化で構成される2フェーズの構成が必要です。

私が採用するアプローチは、他の多くの言語のアプローチと似ています。私は、よく知られているパブリックメソッド(Construct()init()など)に構築コードを配置し、必要に応じて直接呼び出します。

コンストラクターに一致するこれらのメソッドのオーバーロードを作成できます。通常のコンストラクターは、それらを呼び出すだけです。コードに大きなコメントを入れて、これを行っていることを他の人に警告し、重要な構築コードを間違った場所に追加しないようにします。

使用されている構築オーバーロードに関係なく、デストラクタメソッドは1つしかないため、デストラクタを初期化されていないメンバに対して堅牢にするようにしてください。

再初期化できるイニシャライザを作成しないことをお勧めします。初期化されていないメモリと実際の実際のデータを保持しているため、ガベージが含まれているオブジェクトを表示している場合を区別するのは困難です。

最も難しい問題は、仮想メソッドを持つクラスです。この場合、コンパイラは通常、vtable関数テーブルポインタをクラスの開始時に非表示フィールドとしてプラグインします。このポインターは手動で初期化できますが、基本的にはコンパイラー固有の動作に依存しているため、同僚が面白そうに見える可能性があります。

新しい配置は多くの点で壊れています。配列の構築/破壊は1つのケースなので、私はそれを使用しない傾向があります。

0
jls

少なくともあなたが説明している方法では、あなたは通常それをコンストラクタに使用することはないと思います。ただし、異なる名前空間に2つのクラスがある場合は、必要になります。たとえば、これらの2つの構成されたクラス、_Xml::Element_と_Chemistry::Element_の違いを指定するには、.

通常、クラスの名前はスコープ解決演算子と共に使用され、継承されたクラスの親の関数を呼び出します。したがって、Animalから継承するクラスDogがあり、これらのクラスの両方が関数Eat()を異なる方法で定義している場合、「someDog」というDogオブジェクトでAnimalバージョンのeatを使用したい場合があります。私のC++構文は少し錆びていますが、その場合はsomeDog.Animal::Eat()と言うと思います。

0

次のプログラムを検討してください。

template<class T>
double GetAverage(T tArray[], int nElements)
{
T tSum = T(); // tSum = 0

for (int nIndex = 0; nIndex < nElements; ++nIndex)
{
    tSum += tArray[nIndex];
}

// Whatever type of T is, convert to double
return double(tSum) / nElements;
}

これにより、デフォルトのコンストラクターが明示的に呼び出され、変数が初期化されます。

0
Devesh Agrawal