web-dev-qa-db-ja.com

なぜstd :: vector <Abstract Class>を宣言できないのですか?

C#での開発にかなりの時間を費やしたため、インターフェイスとして使用するために抽象クラスを宣言すると、この抽象クラスのベクトルをインスタンス化して子クラスのインスタンスを格納できないことに気付きました。

#pragma once
#include <iostream>
#include <vector>

using namespace std;

class IFunnyInterface
{
public:
    virtual void IamFunny()  = 0;
};

class FunnyImpl: IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};

class FunnyContainer
{
private:
    std::vector <IFunnyInterface> funnyItems;
};

抽象クラスのベクトルを宣言する行により、MS VS2005でこのエラーが発生します。

error C2259: 'IFunnyInterface' : cannot instantiate abstract class

明らかな回避策があります。これは、IFunnyInterfaceを次のものに置き換えることです。

class IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        throw new std::exception("not implemented");
    }
};

これはC++の賢明な回避策ですか?そうでない場合、これを回避するのに役立つboostのようなサードパーティのライブラリはありますか?

これを読んでくれてありがとう!

アンソニー

75
BlueTrin

抽象クラスをインスタンス化することはできません。したがって、抽象クラスのベクターは機能しません。

ただし、抽象クラスへのポインターのベクトルを使用できます。

std::vector<IFunnyInterface*> ifVec;

これにより、実際に多態的な動作を使用することもできます-クラスが抽象的でなくても、値で保存すると オブジェクトのスライス の問題が発生します。

115
Georg Fritzsche

抽象クラスのインスタンス、およびstd :: vectorなどのC++標準ライブラリコンテナは値(つまりインスタンス)を保存できないため、抽象クラスタイプのベクトルを作成することはできません。これを行うには、抽象クラス型へのポインターのベクトルを作成する必要があります。

仮想関数(そもそも抽象クラスが必要な理由)は、ポインターまたは参照を介して呼び出された場合にのみ機能するため、回避策は機能しません。参照のベクトルも作成できないため、これがポインターのベクトルを使用する必要がある2番目の理由です。

C++とC#にはほとんど共通点がないことを理解する必要があります。 C++を学習する場合は、ゼロから始めると考え、KoenigとMooによる Accelerated C++ などの専用のC++チュートリアルをお読みください。

21
anon

この場合、次のコードも使用できません。

std::vector <IFunnyInterface*> funnyItems;

または

std::vector <std::tr1::shared_ptr<IFunnyInterface> > funnyItems;

IS FunnyImplとIFunnyInterfaceの関係がなく、プライベート継承のためにFUnnyImplとIFunnyInterfaceの間に暗黙の変換がないためです。

次のようにコードを更新する必要があります。

class IFunnyInterface
{
public:
    virtual void IamFunny()  = 0;
};

class FunnyImpl: public IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};
6

従来の代替方法は、すでに説明したように、vectorポインターを使用することです。

感謝する人のために、Boostには非常に興味深いライブラリが付属しています:Pointer Containersこれはタスクに完全に適しており、ポインターによって暗示されるさまざまな問題から解放されます。

  • 生涯管理
  • イテレータの二重逆参照

これは、パフォーマンスとインターフェイスの両方の点で、スマートポインターのvectorよりも大幅に優れていることに注意してください。

現在、3番目の選択肢があります。これは、階層を変更することです。ユーザーの断熱性を高めるために、次のパターンが何度も使用されています。

class IClass;

class MyClass
{
public:
  typedef enum { Var1, Var2 } Type;

  explicit MyClass(Type type);

  int foo();
  int bar();

private:
  IClass* m_impl;
};

struct IClass
{
  virtual ~IClass();

  virtual int foo();
  virtual int bar();
};

class MyClass1: public IClass { .. };
class MyClass2: public IClass { .. };

これは非常に簡単で、Pimplのパターンによって強化されたStrategyイディオムのバリエーションです。

もちろん、「真の」オブジェクトを直接操作したくない場合にのみ機能し、ディープコピーを伴います。だから、あなたが望むものではないかもしれません。

6
Matthieu M.

ベクターのサイズを変更するには、デフォルトのコンストラクターとクラスのサイズを使用する必要があり、そのためには具体的になる必要があります。

他の提案としてポインターを使用できます。

2
kennytm

std :: vectorは、タイプを格納するためにメモリを割り当てようとします。クラスが純粋に仮想の場合、ベクトルは割り当てる必要があるクラスのサイズを知ることができません。

回避策を使用すると、vector<IFunnyInterface>をコンパイルできますが、その中のFunnyImplを操作することはできません。たとえば、IFunnyInterface(抽象クラ​​ス)のサイズが20(実際にはわからない)で、FunnyImplのメンバーとコードが多いためサイズが30の場合、20のベクトルに30を収めようとします。

解決策は、「new」でヒープにメモリを割り当て、vector<IFunnyInterface*>にポインタを格納することです。

1
Eric