Почему мы не можем объявить std::vector <AbstractClass>?

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

#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");
    }
};

Является ли это приемлемым решением С++? Если нет, есть ли какая-либо сторонняя библиотека, например boost, которая может помочь мне обойти это?

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

Энтони

Ответ 1

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

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

std::vector<IFunnyInterface*> ifVec;

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

Ответ 2

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

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

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

Ответ 3

Традиционной альтернативой является использование 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.

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

Ответ 4

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

std::vector <IFunnyInterface*> funnyItems;

или

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

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

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

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

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

Ответ 5

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

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

Ответ 6

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

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

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

Ответ 7

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