Чому ми не можемо оголосити 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, яка могла б допомогти мені обійти це?

Дякую, що прочитали це!

Антоній

Відповіді:


127

Ви не можете створити екземпляр абстрактних класів, тому вектор абстрактних класів не може працювати.

Однак ви можете використовувати вектор покажчиків для абстрактних класів:

std::vector<IFunnyInterface*> ifVec;

Це також дозволяє вам фактично використовувати поліморфну ​​поведінку - навіть якщо клас не був абстрактним, зберігання за значенням призведе до проблеми нарізання об'єктів .


5
або ви можете використовувати std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>>, якщо ви не хочете мати справу з життям об'єкта вручну.
Сергій Тепляков

4
Або ще краще, boost :: ptr_vector <>.
Роел

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;

Оскільки між FunnyImpl та IFunnyInterface немає взаємозв'язку IS та не існує неявного перетворення між 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покажчиків a , як уже зазначалося.

Для тих, хто цінує, 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шаблоном.

Звичайно, це працює лише в тому випадку, коли ви не бажаєте безпосередньо маніпулювати "істинними" об'єктами і включає глибоке копіювання. Тож це може бути не те, що ти бажаєш.


1
Дякуємо за посилання Boost та шаблон дизайну
BlueTrin

2

Оскільки для зміни розміру вектора потрібно використовувати конструктор за замовчуванням та розмір класу, що в свою чергу вимагає, щоб він був конкретним.

Ви можете використовувати вказівник, як пропонували інші.


1

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

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

Рішенням було б виділити пам'ять у купі з "новими" та зберегти вказівники в vector<IFunnyInterface*>


Я думав, що це відповідь, але шукайте відповідь gf та нарізання об’єктів, це точно пояснює, що відбуватиметься в контейнері
BlueTrin

Ця відповідь описувала, що могло б статися, але без використання слова "нарізка", тому ця відповідь є правильною. При використанні вектора ptrs нарізання не відбудеться. У цьому вся суть використання ptrs, насамперед.
Роел

-2

Я думаю, що основною причиною цього справді сумного обмеження є той факт, що конструктори не можуть віртуально. Тому компілятор не може генерувати код, який копіює об'єкт, не знаючи його часу під час компіляції.


2
Це не першопричина, і це не "сумне обмеження".

Поясніть, будь ласка, чому, на вашу думку, це не обмеження? Було б непогано мати можливість. І є деякі накладні витрати на програміста, коли він / вона змушений наводити покажчики на контейнер і турбуватися про видалення. Я згоден з тим, що наявність об’єктів різного розміру в одному контейнері погіршить продуктивність.
Девід Грузман,

Відправка віртуальних функцій на основі типу об’єкта, який ви маєте. Вся суть конструкторів у тому, що об'єкта ще немає . Пов’язано з причиною, через яку ви не можете мати статичні віртуальні функції: також немає об’єкта.
MSalters

Я можу сказати, що шаблон контейнера класів потребує не об'єкта, а фабрики класів, і конструктор є його природною частиною.
Девід Грузман,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.