¿Por qué no podemos declarar un std :: vector <AbstractClass>?

88

Habiendo pasado bastante tiempo desarrollando en C #, noté que si declara una clase abstracta con el propósito de usarla como interfaz, no puede crear una instancia de un vector de esta clase abstracta para almacenar instancias de las clases secundarias.

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

La línea que declara el vector de clase abstracta provoca este error en MS VS2005:

error C2259: 'IFunnyInterface' : cannot instantiate abstract class

Veo una solución obvia, que es reemplazar IFunnyInterface con lo siguiente:

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

¿Es esta una solución alternativa aceptable en cuanto a C ++? Si no es así, ¿hay alguna biblioteca de terceros como boost que pueda ayudarme a solucionar esto?

Gracias por leer esto !

Antonio

BlueTrin
fuente

Respuestas:

127

No puede crear instancias de clases abstractas, por lo que un vector de clases abstractas no puede funcionar.

Sin embargo, puede usar un vector de punteros para abstraer clases:

std::vector<IFunnyInterface*> ifVec;

Esto también le permite usar el comportamiento polimórfico, incluso si la clase no fuera abstracta, almacenar por valor conduciría al problema de la división de objetos .

Georg Fritzsche
fuente
5
o puede usar std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>> si no desea manejar la vida útil del objeto manualmente.
Sergey Teplyakov
4
O incluso mejor, boost :: ptr_vector <>.
Roel
7
O ahora, std :: vector <std :: unique_ptr <IFunnyInterface>>.
Kaz Dragon
21

No puede crear un vector de un tipo de clase abstracta porque no puede crear instancias de una clase abstracta y contenedores de la biblioteca estándar de C ++ como std :: vector store values ​​(es decir, instancias). Si quiere hacer esto, tendrá que crear un vector de punteros al tipo de clase abstracta.

Su solución alternativa no funcionaría porque las funciones virtuales (que es la razón por la que desea la clase abstracta en primer lugar) solo funcionan cuando se llaman a través de punteros o referencias. Tampoco puede crear vectores de referencias, por lo que esta es una segunda razón por la que debe usar un vector de punteros.

Debe darse cuenta de que C ++ y C # tienen muy poco en común. Si tiene la intención de aprender C ++, debe pensar en ello como comenzar desde cero y leer un buen tutorial dedicado de C ++ como Accelerated C ++ de Koenig y Moo.


fuente
¡Gracias por recomendar un libro además de responder a la publicación!
BlueTrin
Pero cuando declaras un vector de clases abstractas, ¿no le estás pidiendo que cree ninguna clase abstracta, solo un vector que sea capaz de contener una subclase no abstracta de esa clase? A menos que pase un número al constructor de vectores, ¿cómo puede saber cuántas instancias de la clase abstracta crear?
Jonathan.
6

En este caso, no podemos usar ni siquiera este código:

std::vector <IFunnyInterface*> funnyItems;

o

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

Porque no existe una relación IS A entre FunnyImpl e IFunnyInterface y no hay conversión implícita entre FUnnyImpl e IFunnyInterface debido a la herencia privada.

Debe actualizar su código de la siguiente manera:

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

class FunnyImpl: public IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};
Sergey Teplyakov
fuente
1
La mayoría de la gente miró la herencia privada, creo :) Pero no confundamos el OP aún más :)
Roel
1
Sí. Especialmente después de la frase de inicio del tema: "Habiendo pasado bastante tiempo desarrollando en C #" (donde no hay herencia privada en absoluto).
Sergey Teplyakov
6

La alternativa tradicional es usar una vectorde punteros, como ya se señaló.

Para aquellos que lo aprecian, Boostviene con una biblioteca muy interesante: Pointer Containersque se adapta perfectamente a la tarea y lo libera de los diversos problemas que implican los punteros:

  • gestión de por vida
  • doble desreferenciación de iteradores

Tenga en cuenta que esto es significativamente mejor que uno vectorde los punteros inteligentes, tanto en términos de rendimiento como de interfaz.

Ahora, hay una tercera alternativa, que es cambiar su jerarquía. Para un mejor aislamiento del usuario, he visto varias veces el siguiente patrón utilizado:

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

Esto es bastante sencillo y una variación del Pimplidioma enriquecido por un Strategypatrón.

Funciona, por supuesto, sólo en el caso en el que no desee manipular los objetos "verdaderos" directamente, e implica una copia profunda. Así que puede que no sea lo que deseas.

Matthieu M.
fuente
1
Gracias por la referencia de Boost y el patrón de diseño
BlueTrin
2

Porque para cambiar el tamaño de un vector es necesario utilizar el constructor predeterminado y el tamaño de la clase, que a su vez requiere que sea concreto.

Puede utilizar un puntero como se sugiere.

Kennytm
fuente
1

std :: vector intentará asignar memoria para contener su tipo. Si su clase es puramente virtual, el vector no puede conocer el tamaño de la clase que tendrá que asignar.

Creo que con su solución, podrá compilar un vector<IFunnyInterface>pero no podrá manipular FunnyImpl dentro de él. Por ejemplo, si IFunnyInterface (clase abstracta) es de tamaño 20 (realmente no lo sé) y FunnyImpl es de tamaño 30 porque tiene más miembros y código, terminarás intentando encajar 30 en tu vector de 20

La solución sería asignar memoria en el montón con "nuevo" y almacenar punteros en vector<IFunnyInterface*>

Eric
fuente
Pensé que esta era la respuesta, pero busque la respuesta de gf y el corte de objetos, explica exactamente lo que sucederá dentro del contenedor
BlueTrin
Esta respuesta describió lo que sucedería pero sin usar la palabra 'rebanar', por lo que esta respuesta es correcta. Cuando se utiliza un vector de ptrs, no se producirá ningún corte. Ese es el objetivo de usar ptrs en primer lugar.
Roel
-2

Creo que la causa principal de esta limitación realmente triste es el hecho de que los constructores no pueden virtual. Por lo tanto, el compilador no puede generar código que copie el objeto sin conocer su tiempo en el tiempo de compilación.

David Gruzman
fuente
2
Esta no es la causa raíz, y no es una "limitación triste".
Explique por qué cree que no es una limitación. Sería bueno tener la capacidad. Y hay algo de sobrecarga en el programador cuando se ve obligado a poner punteros al contenedor y preocuparse por una eliminación. Estoy de acuerdo en que tener objetos de diferentes tamaños en el mismo contenedor afectará el rendimiento.
David Gruzman
Las funciones virtuales se envían según el tipo de objeto que tiene. El punto de constructores de todo es que no tienen un objeto todavía . Relacionado con la razón por la que no puede tener funciones virtuales estáticas: tampoco ningún objeto.
MSalters
Puedo decir que la plantilla del contenedor de clases no necesita un objeto, sino una fábrica de clases, y el constructor es una parte natural de ella.
David Gruzman