views:

449

answers:

7

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

+10  A: 

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.

anon
Thank you for recommending a book in addition of replying to the post !
BlueTrin
+23  A: 

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.

Georg Fritzsche
+1 for mentioning slicing, too
xtofl
Thank you for the answer
BlueTrin
or you can use std::vector<std::tr1::shared_ptr<IFunnyInterface> > if you don't want to deal with object lifetime manually.
Sergey Teplyakov
Or even better, boost::ptr_vector<>.
Roel
+2  A: 

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.

KennyTM
A: 

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.

David Gruzman
+1 for explaining why object slicing happens
BlueTrin
This is not the root cause, and it isn't a "sad limitation".
anon
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.
David Gruzman
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.
MSalters
I can say that class container's template need not object, but a class factory, and constructor is a natural part of it.
David Gruzman
A: 

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*>

Eric
I thought this was the answer, but look up for gf reply and object slicing, it explain exactly what will happen within the container
BlueTrin
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.
Roel
+2  A: 

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>";
    }
};
Sergey Teplyakov
Most people looked over the private inheritance I think :) But let's not confuse the OP even more :)
Roel
Yep. Especially after topic starter's phrase: "Having spent quite some time developping in C#" (where no private inheritance at all).
Sergey Teplyakov
+2  A: 

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.

Matthieu M.
Thank you for the Boost reference and the design pattern
BlueTrin