Dlaczego nie możemy zadeklarować std :: vector <AbstractClass>?

88

Spędziwszy trochę czasu na rozwijaniu w C #, zauważyłem, że jeśli zadeklarujesz klasę abstrakcyjną w celu użycia jej jako interfejsu, nie możesz utworzyć instancji wektora tej klasy abstrakcyjnej do przechowywania instancji klas potomnych.

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

Linia deklarująca wektor klasy abstrakcyjnej powoduje ten błąd w MS VS2005:

error C2259: 'IFunnyInterface' : cannot instantiate abstract class

Widzę oczywiste obejście, które polega na zastąpieniu IFunnyInterface następującym:

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

Czy to dopuszczalne obejście C ++ mądre? Jeśli nie, czy istnieje biblioteka innej firmy, taka jak boost, która mogłaby mi pomóc w obejściu tego problemu?

Dziękuję za przeczytanie!

Anthony

BlueTrin
źródło

Odpowiedzi:

127

Nie można utworzyć instancji klas abstrakcyjnych, dlatego wektor klas abstrakcyjnych nie może działać.

Możesz jednak użyć wektora wskaźników do klas abstrakcyjnych:

std::vector<IFunnyInterface*> ifVec;

Pozwala to również na rzeczywiste użycie zachowania polimorficznego - nawet jeśli klasa nie byłaby abstrakcyjna, przechowywanie według wartości prowadziłoby do problemu krojenia obiektów .

Georg Fritzsche
źródło
5
lub możesz użyć std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>>, jeśli nie chcesz zajmować się okresem istnienia obiektu ręcznie.
Sergey Teplyakov
4
Lub jeszcze lepiej, boost :: ptr_vector <>.
Roel
7
A teraz std :: vector <std :: unique_ptr <IFunnyInterface>>.
Kaz Dragon
21

Nie można utworzyć wektora typu klasy abstrakcyjnej, ponieważ nie można tworzyć instancji klasy abstrakcyjnej ani kontenerów biblioteki standardowej C ++, takich jak std :: vector przechowujące wartości (tj. Instancje). Jeśli chcesz to zrobić, będziesz musiał utworzyć wektor wskaźników do typu klasy abstrakcyjnej.

Twoje pole pracy nie zadziałałoby, ponieważ funkcje wirtualne (dlatego chcesz przede wszystkim klasy abstrakcyjnej) działają tylko wtedy, gdy są wywoływane za pomocą wskaźników lub referencji. Nie możesz również tworzyć wektorów odniesień, więc jest to drugi powód, dla którego musisz użyć wektora wskaźników.

Powinieneś zdawać sobie sprawę, że C ++ i C # mają niewiele wspólnego. Jeśli zamierzasz uczyć się C ++, powinieneś pomyśleć o tym jak o rozpoczęciu od zera i przeczytać dobry, dedykowany samouczek C ++, taki jak Accelerated C ++ autorstwa Koeniga i Moo.


źródło
Dziękuję za polecenie książki oprócz odpowiedzi na post!
BlueTrin
Ale kiedy deklarujesz wektor klas abstrakcyjnych, nie prosisz go o utworzenie jakiejkolwiek klasy abstrakcyjnej, tylko wektor, który jest w stanie pomieścić nieabstrakcyjną podklasę tej klasy? O ile nie przekażesz liczby do konstruktora wektorów, skąd może on wiedzieć, ile wystąpień klasy abstrakcyjnej ma utworzyć?
Jonathan.
6

W tym przypadku nie możemy użyć nawet tego kodu:

std::vector <IFunnyInterface*> funnyItems;

lub

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

Ponieważ nie ma relacji IS A między FunnyImpl i IFunnyInterface i nie ma niejawnej konwersji między FUnnyImpl i IFunnyInterface ze względu na dziedziczenie prywatne.

Powinieneś zaktualizować swój kod w następujący sposób:

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

class FunnyImpl: public IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};
Sergey Teplyakov
źródło
1
Myślę, że większość ludzi patrzyła na prywatny spadek :) Ale nie mylmy jeszcze bardziej PO :)
Roel
1
Tak. Zwłaszcza po frazie startera tematu: "Spędziłem trochę czasu na rozwijaniu w C #" (gdzie w ogóle nie ma prywatnego dziedziczenia).
Sergey Teplyakov
6

Tradycyjną alternatywą jest użycie vectorwskaźników, jak już wspomniano.

Dla tych, którzy doceniają, dostępna Boostjest bardzo interesująca biblioteka: Pointer Containersktóra doskonale nadaje się do zadania i uwalnia od różnych problemów wynikających ze wskaźników:

  • zarządzanie na całe życie
  • podwójne dereferencje iteratorów

Zauważ, że jest to znacznie lepsze niż vectorinteligentne wskaźniki, zarówno pod względem wydajności, jak i interfejsu.

Teraz istnieje trzecia alternatywa, która polega na zmianie hierarchii. Dla lepszej izolacji użytkownika widziałem kilka razy następujący wzór:

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 { .. };

Jest to dość proste i odmiana Pimplidiomu wzbogacona Strategywzorem.

Działa to oczywiście tylko w przypadku, gdy nie chcesz bezpośrednio manipulować „prawdziwymi” obiektami i wymaga głębokiego kopiowania. Więc może nie być to, czego sobie życzysz.

Matthieu M.
źródło
1
Dziękuję za odniesienie do Boost i wzorzec projektowy
BlueTrin
2

Ponieważ aby zmienić rozmiar wektora, musisz użyć domyślnego konstruktora i rozmiaru klasy, co z kolei wymaga, aby był on konkretny.

Możesz użyć wskaźnika, jak sugerowano inne.

kennytm
źródło
1

std :: vector spróbuje przydzielić pamięć do przechowywania Twojego typu. Jeśli twoja klasa jest czysto wirtualna, wektor nie może znać rozmiaru klasy, którą będzie musiał zaalokować.

Myślę, że dzięki twojemu obejściu będziesz mógł skompilować plik, vector<IFunnyInterface>ale nie będziesz w stanie manipulować w nim FunnyImpl. Na przykład, jeśli IFunnyInterface (klasa abstrakcyjna) ma rozmiar 20 (naprawdę nie wiem), a FunnyImpl ma rozmiar 30, ponieważ ma więcej członków i kodu, w końcu spróbujesz zmieścić 30 w swoim wektorze 20

Rozwiązaniem byłoby przydzielenie pamięci na stercie za pomocą „nowego” i przechowywanie wskaźników w vector<IFunnyInterface*>

Eric
źródło
Myślałem, że to odpowiedź, ale poszukaj odpowiedzi gf i
cięcia
Ta odpowiedź opisuje, co by się stało, gdyby nie użycie słowa „krojenie”, więc ta odpowiedź jest poprawna. Używając wektora ptrs, nie nastąpi cięcie. O to właśnie chodzi w używaniu ptrs w pierwszej kolejności.
Roel
-2

Myślę, że główną przyczyną tego naprawdę smutnego ograniczenia jest fakt, że konstruktorzy nie potrafią wirtualizować. Z tego powodu kompilator nie może wygenerować kodu kopiującego obiekt bez znajomości jego czasu w czasie kompilacji.

David Gruzman
źródło
2
To nie jest podstawowa przyczyna i nie jest to „smutne ograniczenie”.
Proszę wyjaśnić, dlaczego uważasz, że to nie jest ograniczenie? Byłoby miło mieć taką możliwość. A programista ma pewne obciążenie, gdy jest zmuszony umieścić wskaźniki w kontenerze i martwić się o usunięcie. Zgadzam się, że posiadanie przedmiotów o różnych rozmiarach w tym samym pojemniku pogorszy wydajność.
David Gruzman
Funkcje wirtualne są wysyłane na podstawie typu posiadanego obiektu. Cała sprawa konstruktorów polega na tym, że nie mają jeszcze obiektu . Związane z powodem, dla którego nie możesz mieć statycznych funkcji wirtualnych: również brak obiektu.
MSalters
Mogę powiedzieć, że szablon kontenera klas nie potrzebuje obiektu, ale fabryka klas, a konstruktor jest jego naturalną częścią.
David Gruzman