Why can't we declare a std::vector<AbstractClass>?
Asked Answered
T

7

113

Having spent quite some time developping in C#, I noticed that if you declare an abstract class for the purpose of using it as an interface you cannot instantiate a vector of this abstract class to store instances of the children classes.

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

The line declaring the vector of abstract class causes this error in MS VS2005:

error C2259: 'IFunnyInterface' : cannot instantiate abstract class

I see an obvious workaround, which is to replace IFunnyInterface with the following:

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

Is this an acceptable workaround C++ wise ? If not, is there any third party library like boost which could help me to get around this ?

Thank you for reading this !

Anthony

Tangled answered 29/1, 2010 at 9:17 Comment(0)
A
148

You can't instantiate abstract classes, thus a vector of abstract classes can't work.

You can however use a vector of pointers to abstract classes:

std::vector<IFunnyInterface*> ifVec;

This also allows you to actually use polymorphic behaviour - even if the class wasn't abstract, storing by value would lead to the problem of object slicing.

Annecorinne answered 29/1, 2010 at 9:20 Comment(5)
or you can use std::vector<std::tr1::shared_ptr<IFunnyInterface> > if you don't want to deal with object lifetime manually.Ine
Or even better, boost::ptr_vector<>.Terra
Or now, std::vector<std::unique_ptr<IFunnyInterface>>.Enfold
@KazDragon - You shouldn't create a vector of std::unique_ptr, because unique pointers can't be copied. For example, the compiler will throw an error if you try and call push_back with a unique pointer. It's recommended to use the std::shared_ptr, which will allow the copy-based shifting around that the standard library vector implementation does.Saez
@CodeDoggo You can use std::move on your unique_ptr to move the items into the vector (e.g. v.push_back(std::move(pval))), and the vector can itself be moved around similarly. See godbolt.org/z/jG38fo1PeEnfold
R
26

You can't create a vector of an abstract class type because you cannot create instances of an abstract class, and C++ Standard Library containers like std::vector store values (i.e. instances). If you want to do this, you will have to create a vector of pointers to the abstract class type.

Your workround would not work because virtual functions (which is why you want the abstract class in the first place) only work when called through pointers or references. You cannot create vectors of references either, so this is a second reason why you must use a vector of pointers.

You should realise that C++ and C# have very little in common. If you are intending to learn C++, you should think of it as starting from scratch, and read a good dedicated C++ tutorial such as Accelerated C++ by Koenig and Moo.

Radical answered 29/1, 2010 at 9:20 Comment(3)
Thank you for recommending a book in addition of replying to the post !Tangled
But when you declare a vector of abstract classes, you're not asking it to create any Abstract class, just a vector that is capable of holding a non abstract subclass of that class? Unless you pass a number in to the vectors constructor how can it possibly know how many of the instances of the abstract class to create?Dactylic
Can std::reference_wrapper be used to create a vector of references?Lennox
I
6

In this case we can't use even this code:

std::vector <IFunnyInterface*> funnyItems;

or

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

Because there is no IS A relationship between FunnyImpl and IFunnyInterface and there is no implicit convertion between FUnnyImpl and IFunnyInterface because of private inheritance.

You should update your code as follows:

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

class FunnyImpl: public IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};
Ine answered 29/1, 2010 at 10:57 Comment(2)
Most people looked over the private inheritance I think :) But let's not confuse the OP even more :)Terra
Yep. Especially after topic starter's phrase: "Having spent quite some time developping in C#" (where no private inheritance at all).Ine
S
6

The traditional alternative is to use a vector of pointers, like already noted.

For those who appreciate, Boost comes with a very interesting library: Pointer Containers which is perfectly suited for the task and frees you from the various problems implied by pointers:

  • lifetime management
  • double dereferencing of iterators

Note that this is significantly better than a vector of smart pointers, both in terms of performance and interface.

Now, there is a 3rd alternative, which is to change your hierarchy. For better insulation of the user, I have seen a number of times the following pattern used:

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

This is quite straightforward, and a variation of the Pimpl idiom enriched by a Strategy pattern.

It works, of course, only in the case where you do not wish to manipulate the "true" objects directly, and involves deep-copy. So it may not be what you wish.

Symptom answered 29/1, 2010 at 16:24 Comment(1)
Thank you for the Boost reference and the design patternTangled
T
3

Because to resize a vector you need to use the default constructor and the size of the class, which in turn requires it to be concrete.

You can use a pointer as other suggested.

Terbecki answered 29/1, 2010 at 9:22 Comment(0)
M
1

std::vector will try to allocate memory to contain your type. If your class is purely virtual, the vector cannot know the size of the class it will have to allocate.

I think that with your workaround, you will be able to compile a vector<IFunnyInterface> but you won't be able to manipulate FunnyImpl inside of it. For example if IFunnyInterface (abstract class) is of size 20 (i dont really know) and FunnyImpl is of size 30 because it has more members and code, you will end up trying to fit 30 into your vector of 20

The solution would be to allocate memory on the heap with "new" and store pointers in vector<IFunnyInterface*>

Moshemoshell answered 29/1, 2010 at 9:35 Comment(2)
I thought this was the answer, but look up for gf reply and object slicing, it explain exactly what will happen within the containerTangled
This answer described what would happen but without using the word 'slicing', so this answer is correct. When using a vector of ptrs, no slicing will happen. That's the whole point of using ptrs in the first place.Terra
B
-3

I think that the root cause of this really sad limitation is the fact that constructors can not virtual. Thereof compiler can not generate code which copy the object without knowing its time in the compile time.

Betthel answered 29/1, 2010 at 9:35 Comment(4)
This is not the root cause, and it isn't a "sad limitation".Radical
Please explain why do you think it is not a limitation? It would be nice to have the capability. And there is some overhead on programmer when he/she is forced to put pointers to the container, and worry about a deletion. I do agree that having objects of different sizes in the same container will impair performance.Betthel
Virtual functions dispatch based on the type of the object you have. The entire point of constructors is that don't have an object yet. Related to the reason why you can't have static virtual functions: also no object.Malpighiaceous
I can say that class container's template need not object, but a class factory, and constructor is a natural part of it.Betthel

© 2022 - 2024 — McMap. All rights reserved.