Por que não podemos declarar um std :: vector <AbstractClass>?

88

Tendo passado algum tempo desenvolvendo em C #, percebi que se você declarar uma classe abstrata com o propósito de usá-la como uma interface, não poderá instanciar um vetor dessa classe abstrata para armazenar instâncias das classes filhas.

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

A linha que declara o vetor da classe abstrata causa este erro no MS VS2005:

error C2259: 'IFunnyInterface' : cannot instantiate abstract class

Vejo uma solução alternativa óbvia, que é substituir IFunnyInterface pelo seguinte:

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

Esta é uma solução alternativa aceitável em relação ao C ++? Se não, existe alguma biblioteca de terceiros como boost que poderia me ajudar a contornar isso?

Obrigado por ler isso!

Anthony

BlueTrin
fonte

Respostas:

127

Você não pode instanciar classes abstratas, portanto, um vetor de classes abstratas não pode funcionar.

No entanto, você pode usar um vetor de ponteiros para classes abstratas:

std::vector<IFunnyInterface*> ifVec;

Isso também permite que você use o comportamento polimórfico - mesmo que a classe não seja abstrata, o armazenamento por valor levaria ao problema de divisão de objetos .

Georg Fritzsche
fonte
5
ou você pode usar std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>> se você não quiser lidar com o tempo de vida do objeto manualmente.
Sergey Teplyakov
4
Ou melhor ainda, boost :: ptr_vector <>.
Roel
7
Ou agora, std :: vector <std :: unique_ptr <IFunnyInterface>>.
Kaz Dragon de
21

Você não pode criar um vetor de um tipo de classe abstrata porque você não pode criar instâncias de uma classe abstrata e recipientes de biblioteca padrão C ++ como std :: vector store values ​​(ou seja, instâncias). Se você quiser fazer isso, terá que criar um vetor de ponteiros para o tipo de classe abstrata.

Seu workround não funcionaria porque as funções virtuais (que é o motivo pelo qual você quer a classe abstrata em primeiro lugar) só funcionam quando chamadas por meio de ponteiros ou referências. Você também não pode criar vetores de referências, então esta é a segunda razão pela qual você deve usar um vetor de ponteiros.

Você deve perceber que C ++ e C # têm muito pouco em comum. Se você pretende aprender C ++, deve pensar nisso como começar do zero e ler um bom tutorial dedicado a C ++, como Accelerated C ++ de Koenig e Moo.


fonte
Obrigado por recomendar um livro, além de responder à postagem!
BlueTrin
Mas quando você declara um vetor de classes abstratas, não está pedindo a ele para criar qualquer classe abstrata, apenas um vetor que é capaz de conter uma subclasse não abstrata dessa classe? A menos que você passe um número para o construtor de vetores, como ele pode saber quantas instâncias da classe abstrata criar?
Jonathan.
6

Neste caso, não podemos usar nem mesmo este código:

std::vector <IFunnyInterface*> funnyItems;

ou

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

Porque não há relacionamento IS A entre FunnyImpl e IFunnyInterface e não há conversão implícita entre FUnnyImpl e IFunnyInterface por causa da herança privada.

Você deve atualizar seu código da seguinte maneira:

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

class FunnyImpl: public IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};
Sergey Teplyakov
fonte
1
A maioria das pessoas examinou a herança privada, eu acho :) Mas não vamos confundir o OP ainda mais :)
Roel
1
Sim. Especialmente após a frase do iniciador do tópico: "Tendo gasto algum tempo desenvolvendo em C #" (onde nenhuma herança privada em tudo).
Sergey Teplyakov
6

A alternativa tradicional é usar um vectorde ponteiros, como já foi mencionado.

Para quem gosta, Boostvem com uma biblioteca muito interessante: Pointer Containersque se adapta perfeitamente à tarefa e te livra dos vários problemas que os ponteiros implicam:

  • gerenciamento de vida
  • desreferenciação dupla de iteradores

Observe que isso é significativamente melhor do que vectorponteiros inteligentes, tanto em termos de desempenho quanto de interface.

Agora, há uma terceira alternativa, que é mudar sua hierarquia. Para melhor isolamento do usuário, tenho visto várias vezes o seguinte padrão usado:

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

Isso é bastante direto e uma variação do Pimplidioma enriquecido por um Strategypadrão.

Isso funciona, é claro, apenas no caso em que você não deseja manipular os objetos "verdadeiros" diretamente e envolve cópia profunda. Portanto, pode não ser o que você deseja.

Matthieu M.
fonte
1
Obrigado pela referência de Boost e o padrão de design
BlueTrin
2

Porque para redimensionar um vetor, você precisa usar o construtor padrão e o tamanho da classe, que por sua vez exige que seja concreto.

Você pode usar um ponteiro como outra sugestão.

Kennytm
fonte
1

std :: vector tentará alocar memória para conter seu tipo. Se sua classe for puramente virtual, o vetor não pode saber o tamanho da classe que terá que alocar.

Acho que, com sua solução alternativa, você poderá compilar um, vector<IFunnyInterface>mas não poderá manipular o FunnyImpl dentro dele. Por exemplo, se IFunnyInterface (classe abstrata) for de tamanho 20 (não sei direito) e FunnyImpl for de tamanho 30 porque tem mais membros e código, você vai acabar tentando encaixar 30 em seu vetor de 20

A solução seria alocar memória no heap com "novos" e armazenar ponteiros em vector<IFunnyInterface*>

Eric
fonte
Achei que essa fosse a resposta, mas procure por resposta gf e
divisão de
Essa resposta descreveu o que aconteceria, mas sem usar a palavra 'fatiar', portanto, a resposta está correta. Ao usar um vetor de ptrs, nenhum corte acontecerá. Esse é o ponto principal de usar ptrs em primeiro lugar.
Roel
-2

Acho que a causa raiz dessa limitação realmente triste é o fato de que os construtores não podem ser virtuais. Portanto, o compilador não pode gerar código que copie o objeto sem saber seu tempo no momento da compilação.

David Gruzman
fonte
2
Esta não é a causa raiz e não é uma "triste limitação".
Explique por que você acha que não é uma limitação? Seria bom ter essa capacidade. E há alguma sobrecarga no programador quando ele / ela é forçado a colocar ponteiros no contêiner e se preocupar com uma exclusão. Eu concordo que ter objetos de tamanhos diferentes no mesmo contêiner prejudicará o desempenho.
David Gruzman
As funções virtuais são despachadas com base no tipo de objeto que você possui. Todo o ponto de construtores é que não têm um objeto ainda . Relacionado ao motivo pelo qual você não pode ter funções virtuais estáticas: também nenhum objeto.
MSalters em
Posso dizer que o modelo do contêiner de classe não precisa ser um objeto, mas uma fábrica de classe, e o construtor é uma parte natural disso.
David Gruzman