Почему мы не можем объявить std :: vector <AbstractClass>? - PullRequest
73 голосов
/ 29 января 2010

Потратив довольно много времени на разработку в 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, которая может помочь мне обойти это?

Спасибо, что прочитали это!

Anthony

Ответы [ 7 ]

113 голосов
/ 29 января 2010

Вы не можете создавать экземпляры абстрактных классов, поэтому вектор абстрактных классов не может работать.

Однако вы можете использовать вектор указателей для абстрагирования классов:

std::vector<IFunnyInterface*> ifVec;

Это также позволяет вам фактически использовать полиморфное поведение - даже если класс не был абстрактным, сохранение по значению приведет к проблеме среза объекта .

21 голосов
/ 29 января 2010

Вы не можете создать вектор типа абстрактного класса, потому что вы не можете создавать экземпляры абстрактного класса, и контейнеры стандартной библиотеки C ++, такие как значения хранилища std :: vector (т.е. экземпляры). Если вы хотите сделать это, вам нужно будет создать вектор указателей на абстрактный тип класса.

Ваше рабочее пространство не будет работать, потому что виртуальные функции (именно поэтому вы хотите, чтобы абстрактный класс был в первую очередь) работают только при вызове через указатели или ссылки. Вы также не можете создавать векторы ссылок, так что это вторая причина, по которой вы должны использовать вектор указателей.

Вы должны понимать, что C ++ и C # имеют очень мало общего. Если вы собираетесь изучать C ++, вам следует подумать о том, что он начинается с нуля, и прочитайте хороший специализированный учебник по C ++, такой как Accelerated C ++ , автор Koenig and Moo.

6 голосов
/ 29 января 2010

Традиционной альтернативой является использование vector указателей, как уже отмечалось.

Для тех, кто ценит, Boost поставляется с очень интересной библиотекой: Pointer Containers, которая идеально подходит для этой задачи и освобождает вас от различных проблем, связанных с указателями:

  • управление жизненным циклом
  • двойная разыменование итераторов

Обратите внимание, что это значительно лучше, чем vector интеллектуальных указателей, как с точки зрения производительности, так и интерфейса.

Теперь есть третья альтернатива, которая заключается в изменении вашей иерархии. Для лучшей изоляции пользователя я несколько раз видел следующую схему:

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 голосов
/ 29 января 2010

В этом случае мы не можем использовать даже этот код:

std::vector <IFunnyInterface*> funnyItems;

или

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

Так как между FunnyImpl и IFunnyInterface нет взаимосвязи IS и не существует явного преобразования между FUnnyImpl и IFunnyInterface из-за частного наследования.

Вы должны обновить свой код следующим образом:

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

class FunnyImpl: public IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};
2 голосов
/ 29 января 2010

Поскольку для изменения размера вектора необходимо использовать конструктор по умолчанию и размер класса, что, в свою очередь, требует, чтобы он был конкретным.

Вы можете использовать указатель как предложено другим.

1 голос
/ 29 января 2010

std :: vector будет пытаться выделить память для размещения вашего типа. Если ваш класс чисто виртуальный, вектор не может знать размер класса, который ему придется выделить.

Я думаю, что с вашим обходным путем вы сможете скомпилировать vector<IFunnyInterface>, но вы не сможете манипулировать FunnyImpl внутри него. Например, если IFunnyInterface (абстрактный класс) имеет размер 20 (я действительно не знаю), а FunnyImpl имеет размер 30, потому что он имеет больше членов и код, вы в конечном итоге попытаетесь вписать 30 в ваш вектор 20

Решением было бы выделить память в куче с помощью «new» и хранить указатели в vector<IFunnyInterface*>

0 голосов
/ 29 января 2010

Я думаю, что коренной причиной этого действительно печального ограничения является тот факт, что конструкторы не могут быть виртуальными. Вследствие этого компилятор не может генерировать код, который копирует объект, не зная его времени во время компиляции.

...