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

88

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

Спасибо за чтение этого !

Энтони

BlueTrin
источник

Ответы:

127

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

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

std::vector<IFunnyInterface*> ifVec;

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

Георг Фриче
источник
5
или вы можете использовать std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>>, если вы не хотите иметь дело с временем жизни объекта вручную.
Сергей Тепляков
4
Или, что еще лучше, boost :: ptr_vector <>.
Roel
7
Или теперь std :: vector <std :: unique_ptr <IFunnyInterface>>.
Kaz Dragon
21

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

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

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


источник
Спасибо, что порекомендовали книгу в дополнение к ответу на сообщение!
BlueTrin
Но когда вы объявляете вектор абстрактных классов, вы не просите его создать какой-либо абстрактный класс, а просто вектор, который может содержать не абстрактный подкласс этого класса? Если вы не передадите число в конструктор векторов, как он может узнать, сколько экземпляров абстрактного класса нужно создать?
Джонатан.
6

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

std::vector <IFunnyInterface*> funnyItems;

или

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

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

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

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

class FunnyImpl: public IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};
Сергей Тепляков
источник
1
Думаю, большинство людей смотрели на частное наследование :) Но давайте не будем путать ОП еще больше :)
Роэл
1
Ага. Особенно после фразы автора темы: «Потратив довольно много времени на разработку на C #» (где вообще нет частного наследования).
Сергей Тепляков
6

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

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

Матье М.
источник
1
Спасибо за ссылку на Boost и шаблон дизайна
BlueTrin
2

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

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

Kennytm
источник
1

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

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

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

Эрик
источник
Я думал, что это был ответ, но поищите ответ gf и нарезку объекта, он точно объясняет, что произойдет в контейнере
BlueTrin
В этом ответе описано, что произойдет, но без использования слова «нарезка», так что это правильный ответ. При использовании вектора ptrs нарезки не произойдет. В этом и весь смысл использования ptrs.
Roel
-2

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

Давид Грузман
источник
2
Это не основная причина и не «печальное ограничение».
Объясните, пожалуйста, почему, по вашему мнению, это не ограничение? Было бы неплохо иметь возможность. И есть некоторые накладные расходы на программиста, когда он / она вынужден помещать указатели в контейнер и беспокоиться об удалении. Я согласен с тем, что наличие объектов разных размеров в одном контейнере снизит производительность.
Давид Грузман
Отправка виртуальных функций в зависимости от типа имеющегося у вас объекта. Вся суть конструкторов в том, что у них еще нет объекта . Связано с причиной, по которой у вас не может быть статических виртуальных функций: также нет объекта.
MSalters
Я могу сказать, что шаблону контейнера класса нужен не объект, а фабрика классов, и конструктор является ее естественной частью.
Давид Грузман