[C++]Wierd problem - abstract function is unresolved external symbol

Started by
8 comments, last by someboddy 15 years, 4 months ago
Hello everyone. I'm making a resource manager for my game to simplify the loading and unloading of various resources. The idea is that all textures, game object files, level files ext. will inherit from a resource interface class, so the resource manager will treat them all equally. Here is the declaration of that resource interface class:
[source lang=cpp]
class SIResource
{
protected:
	unsigned int id;
public:
	SIResource();
	virtual ~SIResource();
	virtual bool load()=0;
	virtual bool isLoaded() const=0;
	virtual void unload()=0;
	virtual unsigned int getID() const;
};

And here is the implementation:
[source lang=cpp]
SIResource::SIResource()
{
	id=0;
}
SIResource::~SIResource()
{
	unload();
}

unsigned int SIResource::getID() const
{
	return id;
}

When I try to build it in Visual C++ 2008, I get the following error message: SCommonInterfaces.obj : error LNK2019: unresolved external symbol "public: virtual void __thiscall SIResource::unload(void)" (?unload@SIResource@@UAEXXZ) referenced in function "public: virtual __thiscall SIResource::~SIResource(void)" (??1SIResource@@UAE@XZ) WTF? True, the unload function is not implemented, but I did declared it as an abstract function(I know C++ has a different term for it, but I can't remember it right now, and the Java term sounds better anyways) so it shouldn't be an unresolved external symbol. Or am I wrong?
-----------------------------------------Everyboddy need someboddy!
Advertisement
Calling virtual functions in the constructor / destructor is usually a bad idea. Because you're calling the unload() function from your destructor, the vtable would have been destroyed, meaning the only version function you can call would be that in SIResource - which is pure virtual and therefore undefined.
Quote:Original post by Evil Steve
Calling virtual functions in the constructor / destructor is usually a bad idea. Because you're calling the unload() function from your destructor, the vtable would have been destroyed, meaning the only version function you can call would be that in SIResource - which is pure virtual and therefore undefined.


Additional reading.
Remember that destructor calls are in the reverse order of destruction. What you want to do is impossible, just like calling pure virtual functions in a constructor.

Let us imagine if it did compile:
class Texture : public Resourse // the hell is a SIResourse ? =P{	std::vector<char> imagedata;public:	// How is this function useful?	// Surely we need to pass it something to load from...	// A path or a stream or something	virtual bool load();	virtual bool isLoaded() const { return imagedata.size(); }	virtual void unload() { imagedata.clear(); }};

Ok, so let us look at destruction.

First, Texture::~Texture is called. Then std::vector<char>::~vector<char>(). Next up, we are in Resource::~Resource(). We call unload()... and std::vector<char>::clear() is called on a value that has already been destroyed!

This is obviously bad. A better idea would be to use RAII, the constructor obtains whatever data, the destructor frees it, and we either implement or disable copying (to follow the rule of three).

No need for isLoaded(), because if we have successfully constructed a resource it must be loaded. If we can't, we can throw an exception.

Possible good read. Others.
Thanks for the help guys. I guess I'll just abandon the destructor here. I prefer using load and unload instead of constructors and destructors because I'm going to load files at levels they are needed and unload them at levels they are not needed - cause there is no point at storing the whole game data on the memory at once - so a single resource object is going to be loaded and unloaded several times on each run of the program(hence the need for the isLoaded function).
-----------------------------------------Everyboddy need someboddy!
Quote:Original post by someboddy
Thanks for the help guys. I guess I'll just abandon the destructor here. I prefer using load and unload instead of constructors and destructors because I'm going to load files at levels they are needed and unload them at levels they are not needed - cause there is no point at storing the whole game data on the memory at once - so a single resource object is going to be loaded and unloaded several times on each run of the program(hence the need for the isLoaded function).


You can do that:
Texture texture("initial_texture.format");texture = Texture("next_texture.format");texture = Texture("yet_another_texture.format");

All you need is to correctly follow the rule of three I linked above. You can even implement the texture as a lightweight interface to the actual texture data, avoiding excessive copies.
Quote:Original post by Evil Steve
Because you're calling the unload() function from your destructor, the vtable would have been destroyed, meaning the only version function you can call would be that in SIResource - which is pure virtual and therefore undefined.


Pure virtual doesn't necessarily mean undefined. It only means that the definition is optional unless used. For instance, pure virtual destructors must be defined.
struct A {  virtual void foo(void) = 0;  virtual ~A() = 0;};// perfectly legalvoid A::foo(void) {}// not only legal, but required if any concrete class derives from AA::~A() {}
Quote:Original post by rip-off
Quote:Original post by someboddy
Thanks for the help guys. I guess I'll just abandon the destructor here. I prefer using load and unload instead of constructors and destructors because I'm going to load files at levels they are needed and unload them at levels they are not needed - cause there is no point at storing the whole game data on the memory at once - so a single resource object is going to be loaded and unloaded several times on each run of the program(hence the need for the isLoaded function).


You can do that:
*** Source Snippet Removed ***
All you need is to correctly follow the rule of three I linked above. You can even implement the texture as a lightweight interface to the actual texture data, avoiding excessive copies.


I have failed to understand the point of your example code, but my texture class IS a lightweight interface to the actual texture data. That's why it needs unload and isLoaded functions - so I can load and unload the texture itself as needed while the interface object remains on the memory at all time.

About that rule of 3 of yours - I'm 100% positive I don't need copy constructor nor copy assignment operator for any resource class I have - after all, there should be only one of each resource loaded - and after giving it more thought, I figured I don't need a destructor either, which kind of proves that rule. Happy?
-----------------------------------------Everyboddy need someboddy!
Quote:
I have failed to understand the point of your example code

The point was that even without having "load/unload/isLoaded" functions your class can still present the same functionality.

Just use RAII.

I don't see why I would ever want a Texture that didn't have any texture data. To me, such a class should be called a "maybe_texture". Just like std::string. The string is there once it is created. If you want a maybe_string, use std::string *, or a smart pointer.

So, write a class that *is* a texture. If you have one, you *have* a texture. Hell, write a lightweight wrapper for the actual texture data, to avoid copies:
class ActualTextureData{public:    ActualTextureData(ResourceLoader loader, ...); // whatever args you need    ~ActualTextureData();private:    // forbid copies    ActualTextureData(const ActualTextureData &);    ActualTextureData &operator=(const ActualTextureData &);    // whatever private data is required};class Texture // lightweight class{    Texture(ResourceLoader loader, ...); // again, whatever other argsprivate:    boost::shared_ptr<ActualTextureData> actual_texture;};

So, lets examine this class.

Load()
- create a texture instance, fully loaded. Texture tex(loader,"file.png"
- to change the file, with an existing texture instance: tex = Texture(loader,"other.png");

Unload()
- the texture will be unloaded when it goes out of scope.
- the current texture data will be unloaded if assigned to by a new texture

isLoaded()
- always true.

Now, if we really desperately want a maybe_texture, we can use boost::shared_ptr<Texture> ptr. It is "unloaded" if the pointer is null. In all other cases it is loaded. You can force unloading: ptr.reset()

This example uses the texture class. It can be expanded to a level, a world, even an entire game. Yes, it takes a bit of work. If you are used to dealing with "init" and "destroy" functions, it takes effort to think about such a system. But it has its benefits.

Quote:
About that rule of 3 of yours

It isn't mine. Look at the references in that wikipedia page again. Does the name Bjarne Stroustrup ring any bells [smile]

Quote:
I'm 100% positive I don't need copy constructor nor copy assignment operator for any resource class I have - after all, there should be only one of each resource loaded - and after giving it more thought, I figured I don't need a destructor either

Remember, if you don't provide them, the compiler will be more than happy to. Any the compiler tends to do an extremely poor job with complex types.
Quote:Original post by rip-off
I don't see why I would ever want a Texture that didn't have any texture data. To me, such a class should be called a "maybe_texture".


Actually, the resource interface itself is not complete yet. I did not mention it before because it was irrelevant to my problem, but since you want to know why I want unloaded textures/geometry objects/levels, I will elaborate more about the concept of my resource manager.

The idea is to make an unloaded resource object for every resource at the beginning of the game. Beside it's current functionality the resource interface will also have a "required" flag. Each level will have a list of all resources needed for that level. At the beginning of each level, the "required" flag for each resource will be set to false. Than the the program will check the resource list of the level that is going to be loaded, and sets each needed resource's "required" flag to true. If a resource depends on another resource it will set it's "required" flag to true as well(for example a geometry resource will set it's texture's "required" flag to true). After that, I loop through all the resource objects. If isLoaded()==isNeeded() - I do nothing. If the resource is needed but not loaded - I load the resource. If the resource is loaded but not needed - I unload it.



Makes sense now?
-----------------------------------------Everyboddy need someboddy!

This topic is closed to new replies.

Advertisement