Click to See Complete Forum and Search --> : Abstract class and vector issue
alexin
February 13th, 2008, 04:39 AM
I have an abstract class, SceneObject declared as follow:
#ifndef SCENE_OBJECT_H
#define SCENE_OBJECT_H
#include "Application.h"
class SceneObject{
public:
SceneObject(void);
virtual ~SceneObject(void);
public:
virtual void update(Application* app)=0;
virtual void render(Application* app)=0;
};
#endif // SCENE_OBJECT_H
There are two more that derive from the above, an implementation...
#ifndef GROUP_H
#define GROUP_H
#include <vector>
#include "SceneObject.h"
class Group : public SceneObject{
private:
std::vector<SceneObject> children;
public:
Group(void);
~Group(void);
public:
void update(Application* app);
void render(Application* app);
};
#endif // GROUP_H
... and a supposed more specialized abstract class:
#ifndef ACTOR_H
#define ACTOR_H
#include "SceneObject.h"
class Actor : SceneObject{
public:
Actor(void);
~Actor(void);
public:
virtual void update(Application* app)=0;
virtual void render(Application* app)=0;
};
#endif // ACTOR_H
As you can see, Group has a vector of SceneObjects that will eventually contain Groups or Actors.
On compile, the compiler yields
c:\program files\microsoft visual studio 9.0\vc\include\vector(716) : error C2259: 'SceneObject' : cannot instantiate abstract class
due to following members:
'void SceneObject::update(Application *)' : is abstract
c:\documents and settings\alexandre\my documents\visual studio 2008\projects\ocaso\ocaso\sceneobject.h(13) : see declaration of 'SceneObject::update'
'void SceneObject::render(Application *)' : is abstract
c:\documents and settings\alexandre\my documents\visual studio 2008\projects\ocaso\ocaso\sceneobject.h(14) : see declaration of 'SceneObject::render'
c:\documents and settings\alexandre\my documents\visual studio 2008\projects\ocaso\ocaso\group.h(10) : see reference to class template instantiation 'std::vector<_Ty>' being compiled
with
[
_Ty=SceneObject
]
Any help is welcome.
JohnW@Wessex
February 13th, 2008, 05:07 AM
As your SceneObject is an abstract class the compiler cannot create a vector of them in the line std::vector<SceneObject> children;
What you need is
std::vector<SceneObject *> children;
Even if your base class wasn't abstract, the original vector would still not have worked as any derived classes of SceneObject added to the vector would have been 'sliced' (All extra information declared in the derived object would be lost as each would be up-converted to SceneObject)
alexin
February 13th, 2008, 05:52 AM
So I'll be getting a pointer to SceneObject with the possibility to down-cast it...
I think I'll probably run into more problems but it's fine.
Thank you!
JohnW@Wessex
February 13th, 2008, 06:02 AM
You don't need to downcast if you're only using the overridden update and render functions.
children[i]->update(...);
This will call the correct overridden function for the derived class.
JohnW@Wessex
February 13th, 2008, 06:07 AM
I assume the code for Group will of the form...
void Group::render(Application* app)
{
std::vector<SceneObject *>::iterator itr = children.begin();
while (itr != children.end())
{
(*itr)->render(app);
++itr;
}
}
Graham
February 13th, 2008, 10:08 AM
Also note that you've got class Actor deriving privately from SceneObject, so it can't be pointed to by a SceneObject pointer. You probably want public derivation.
alexin
February 13th, 2008, 12:19 PM
You don't need to downcast if you're only using the overridden update and render functions.
I'm aware, thanks :)
Also note that you've got class Actor deriving privately from SceneObject, so it can't be pointed to by a SceneObject pointer. You probably want public derivation.
I don't see what you mean by that...
Graham
February 13th, 2008, 12:32 PM
class Actor : SceneObject{
classes derive privately by default, so the above is equivalent to
class Actor : private SceneObject{
Private derivation is not visible outside of the (derived) class, so external code cannot assign a pointer to Actor to a pointer to SceneObject, therefore, you can't store an actor in your vector<SceneObject*>.
alexin
February 13th, 2008, 12:41 PM
Oh, I didn't know about that.
Thanks for the note, you've probably saved me some headaches.
EDIT:
I've applied the suggested modifications, you guys told me, but I've another problem now. the compilation is successful but when linking...
Actor.obj : error LNK2019: unresolved external symbol "public: virtual __thiscall SceneObject::~SceneObject(void)" (??1SceneObject@@UAE@XZ) referenced in function "public: virtual __thiscall Actor::~Actor(void)" (??1Actor@@UAE@XZ)
Group.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall SceneObject::~SceneObject(void)" (??1SceneObject@@UAE@XZ)
SceneObject.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall SceneObject::~SceneObject(void)" (??1SceneObject@@UAE@XZ)
Note: The Group class declaration misses a ': public SceneObject'; my code is corrected.
JohnW@Wessex
February 14th, 2008, 02:42 AM
Looks like you never created a definition for the SceneObject destructor. As SceneObject is abstract I'm pretty sure you can define it as a pure virtual, as long as a destructor is defined in the derived class.
Graham
February 14th, 2008, 03:42 AM
You can declare a destructor as pure virtual, but you have to give it a definition - you can't get away with not defining a destructor that you've declared - that's one function that will definitely be called. In this case, however, I would suggest that simple inline blank destructor is all that's needed:
class SceneObject
{
public:
virtual ~SceneObject() {}
//...
};
alexin
February 14th, 2008, 08:35 AM
Then subclasses can decide either to define their own destructor or not.
Thanks guys!
code_carnage
February 14th, 2008, 09:14 AM
Subclass doesnt need an destructor if it has got no business...
BUt its always better to give one destructor even if its a blank..
like ~SubClass(){};
laserlight
February 14th, 2008, 09:21 AM
BUt its always better to give one destructor even if its a blank..
like ~SubClass(){};
What's your rationale for that?
code_carnage
February 14th, 2008, 09:41 AM
It just gives out an impression that class creator has not forgotten to implement destructor.. He has not missed it...
If I dont see a destructor I would wonder whether he forgot to implement one...???
Graham
February 14th, 2008, 10:50 AM
If I don't see a destructor, then I'll look at the class definition to see if one is necessary, then wonder whether the author forgot. And check the copy constructor and copy assignment at the same time.
STLDude
February 14th, 2008, 03:13 PM
It just gives out an impression that class creator has not forgotten to implement destructor.. He has not missed it...
If I dont see a destructor I would wonder whether he forgot to implement one...???
But why is that important to me as user of a code? IMO, if class does not need destructor, then don't litter the code.
TheCPUWizard
February 14th, 2008, 03:35 PM
In the general case, I completely agree with not littering the code. Don't implement a destructor if it is not necessary. The ONLY reason for an empty destructor is if you are intending the class to be inherited. The general rule I follow:
1) Class is NOT designed to be inherited: Either no explicit desctructor or a non-virtual distructor.
2) Class is designed to be inherited but is of use by itself. A virtual (non-pure) destructor (empty if necessary)
3) Class is designed to be inherited and is NOT of use by itself (it is conceptually abstract): A Pure Virtual Destructor (empty if necessary).
One condition that often occurs in (other peoples implementations. if the following:
AbstractBase
-- Derived1 (intended for further drivation if necessary)
-- Derived2 (not structured for further derivation)
Since C++ does not (currently) have a "sealed" construct, it can be difficult to indicate that Derived2 should NOT be derived from....
Since my design pattern uses the destructor for all of the other conditions, and that is where I look for a quick glance at the "inheritance intent", I will sometimes put an emp-ty destructor in a derived class which simply contains the comment
~YadaYada()
{
/* This Class should NOT be used as a base class */"
}
Graham
February 15th, 2008, 03:35 AM
I'd add a number 4 to that list:
4) Class is designed to be inherited, is NOT of use by itself and its implementation is strictly hidden (i.e. it's an exported interface to a "black-box" component): a non-virtual, protected destructor. All responsibility for the lifetime of the object is taken by the owning component. Client code must not be allowed to call delete on a pointer to this class, since there is no guarantee that a) the implementation is not doing other things, b) the client code is the only code using that pointer and c) the target object may not have been created with new.
TheCPUWizard
February 15th, 2008, 07:03 AM
Regarding "#4".... :thumb: That is a dfinate real world design pattern (although one I thing occurws with lower frequency than #1-#3).
Do you have any better suggestions for the marking of a "leaf" class in a hierarchy than the "coment in descructor" approach? I have been searching for one for over a decade....
Graham
February 15th, 2008, 05:12 PM
Scott Meyers's advice is that all non-leaf classes should be abstract. So, on that basis, a lack of pure virtuals indicates a leaf class. It's a good idea, and the arguments in its favour are compelling, but it can be difficult to achieve in practice.
I should add that I like, wherever possible, to construct components whose only visible classes are pure abstract "interfaces", so I tend to use the protected, non-virtual destructor quite a lot. The only problem that that produces is all the idiot compilers that warn me about a class with virtual functions and non-virtual destructor. HELLO! It's protected, so SHUT UP about it.
TheCPUWizard
February 15th, 2008, 05:34 PM
Scott Meyers's advice is that all non-leaf classes should be abstract. So, on that basis, a lack of pure virtuals indicates a leaf class. It's a good idea, and the arguments in its favour are compelling, but it can be difficult to achieve in practice.
I should add that I like, wherever possible, to construct components whose only visible classes are pure abstract "interfaces", so I tend to use the protected, non-virtual destructor quite a lot. The only problem that that produces is all the idiot compilers that warn me about a class with virtual functions and non-virtual destructor. HELLO! It's protected, so SHUT UP about it.
Again agreeing with you (on both points) :D
For those who are willing to use a microsoft extension, you can ENFORE pure abstract interfaces: __interface (http://msdn2.microsoft.com/en-us/library/50h7kwtb(VS.71).aspx) (V7.1 and later only)
Regarding Scott Meyers' architectural pattern, I try to follow it when ever possible. But you are right it can be difficult to achieve in practice. Worse, it is (to the best of my knowledge) impossible to enforce.
One way to help is the following (abbreviated) Design Pattern:
1) Create Abstract Base Class
2) Create "General" derived leaf class which encapsulates a pointer to a "Specific" derived leaf class. All operations are directed to the encapsulated instance
3) Create as many "Specific" derived leaf classes as needed. Provide a conversion operator to return a "General" class instance.
Now, you can NOT create the abstrace base class but you CAN create instances of the "General" leaf class. Since you can create instances, you can easily create STL "value" (not pointer) collections of these instances. This neatly avoids the memory management issues with collections of pointers (where the collection conceptually owns the items as it does with value collections), and provides performance that is equivilant.
codeguru.com
Copyright Internet.com Inc., All Rights Reserved.