Skip to main content
GameDev.net gamedev.net
🔒 Locked

Casting Problem

Started by Flyverse May 13, 2015 at 10:31 AM 13 replies 3.3k views
Original Post
Flyverse
Flyverse

Hey guys,

I'm writing a simple abstraction layer between my game and the render-engine. Unfortunately, I ran into a problem: I can't manage to cast an abstract class to its subclass implementation. (Seriously, Java Interfaces & Abstract classes were so much easier....)

First things first: Basically I want to substitute the render-engine initiationcall


oxygine::core::init(&oxygine::core::init_desc);

with my abstract function


GraphicsFactory::initRenderEngine(GraphicsFactory::createInitiationSettings(args));

Here is my code:

IInitationSettings.h


#pragma once
class IInitiationSettings {
public:
	virtual void setTitle(const char* title) = 0;
	virtual void setVsync(bool vsync) = 0;
	virtual void setFullscreen(bool fullscreen) = 0;
	virtual void setWidth(int width) = 0;
	virtual void setHeight(int height) = 0;
};

IInitationSettings.cpp


#include "IInitiationSettings.h"

OxygineInitiationSettings.h


#pragma once
#include "IInitiationSettings.h"
#include "core/oxygine.h"

class OxygineInitiationSettings : public IInitiationSettings, public oxygine::core::init_desc {

public:
	OxygineInitiationSettings();
	OxygineInitiationSettings(const char* title, bool vsync, bool fullscreen, int width, int height);

	void setTitle(const char* title);
	void setVsync(bool vsync);
	void setFullscreen(bool fullscreen);
	void setWidth(int width);
	void setHeight(int height);
};

OxygineInitiationSettings.cpp


#include "OxygineInitiationSettings.h"

OxygineInitiationSettings::OxygineInitiationSettings() : OxygineInitiationSettings("No title specified", true, true, 500, 500){}

OxygineInitiationSettings::OxygineInitiationSettings(const char* title, bool vsync, bool fullscreen, int width, int height) : oxygine::core::init_desc() {
	this->title = title;
	this->vsync = vsync;
	this->fullscreen = fullscreen;
	this->w = width;
	this->h = height;
}

void OxygineInitiationSettings::setTitle(const char* title){
	this->title = title;
}
void OxygineInitiationSettings::setVsync(bool vsync){
	this->vsync = vsync;
}
void OxygineInitiationSettings::setFullscreen(bool fs){
	this->fullscreen = fs;
}
void OxygineInitiationSettings::setWidth(int w){
	this->w = w;
}
void OxygineInitiationSettings::setHeight(int h){
	this->h = h;
}

And finally, the code in GraphicsFactory (Only the relevant code from both the header and the .cpp):


static void init(IInitiationSettings& initSettings);
static IInitiationSettings& createInitiationSettings(const char* title, bool vsync, bool fullscreen, int width, int height);

IInitiationSettings& GraphicsFactory::createInitiationSettings(const char* title, bool vsync, bool fullscreen, int width, int height){
#ifdef USE_OXYGINE_RENDERING
	return OxygineInitiationSettings(title, vsync, fullscreen, width, height);
#endif
}

void GraphicsFactory::init(IInitiationSettings& initSettings){
#ifdef USE_OXYGINE_RENDERING
	OxygineInitiationSettings settings = dynamic_cast<OxygineInitiationSettings&>(initSettings);
	oxygine::core::init(&settings);
        //THESE TWO LINES DON'T WORK. I just can't cast the IInitiationSettings, which in reality IS
        //a OxygineInitiationSettings-Instance, to OxygineInitiationSettings...
#endif
}

The error is in the GraphicsFactory::init implementation. Since USE_OXYGINE_RENDERING is defined, I can safely assume that initSettings is of the type OxygineInitiationSettings, and thus I want to cast it to that - Which isn't working...

Would be awesome if someone could help.

Kind regards

rave3d
rave3d

What do you mean by not working?
Are you getting the error at compile time or at runtime?

From a quick view without having it tested it seems that you are casting an object to a reference here:


OxygineInitiationSettings settings = dynamic_cast<OxygineInitiationSettings&>(initSettings);
Ashaman73
Ashaman73

From a quick view without having it tested it seems that you are casting an object to a reference here:

OxygineInitiationSettings settings = dynamic_cast(initSettings);

Either define a copy-constructor or make settings a reference:


OxygineInitiationSettings& settings = dynamic_cast...

Thinking about it, you don't want to initialize a copy of the settings, right, so make it a reference.

Flyverse
Flyverse

@mgubisch

With the version I posted in the, uh, first post, I get a runtime error: I can't access the object (Violation Error) because it is null, thus indicating me that the casting did not work. In earlier versions where I just tried to ((Cast) like_that), I got a compile&IntelliJ error

@Ashaman73

Does not work, I still get access violation..

BitMaster
BitMaster
I don't think that will work at all. dynamic_cast can, by definition, fail to convert types. If it fails it returns a null-pointer. If you are certain a cast should always work then you should use static_cast, not dynamic_cast.

So either
OxygineInitiationSettings* settings = dynamic_cast<OxygineInitiationSettings*>(&initSettings);
if (settings != nullptr) ...
or
OxygineInitiationSettings* settings = static_cast<OxygineInitiationSettings*>(&initSettings);
...
BitMaster
BitMaster

@Ashaman73
Does not work, I still get access violation..


If built with suitable debug symbols, what does the stack trace of your debugger look like at the crash point?
Ashaman73
Ashaman73

IInitiationSettings& GraphicsFactory::createInitiationSettings(const char* title, bool vsync, bool fullscreen, int width, int height){
#ifdef USE_OXYGINE_RENDERING
	return OxygineInitiationSettings(title, vsync, fullscreen, width, height);
#endif
}

Please remember, that your object exists only temporary and you need a copy-constructor on the calling site to keep it (not a reference).

Therefor this will not work in my opinion:


GraphicsFactory::initRenderEngine(GraphicsFactory::createInitiationSettings(args));

What happens if you change your code to this:


IInitiationSettings& GraphicsFactory::createInitiationSettings(const char* title, bool vsync, bool fullscreen, int width, int height){
#ifdef USE_OXYGINE_RENDERING
	return *(new OxygineInitiationSettings(title, vsync, fullscreen, width, height));
#endif
}
BitMaster
BitMaster




IInitiationSettings& GraphicsFactory::createInitiationSettings(const char* title, bool vsync, bool fullscreen, int width, int height){
#ifdef USE_OXYGINE_RENDERING
	return OxygineInitiationSettings(title, vsync, fullscreen, width, height);
#endif
}
Please remember, that your object exists only temporary and you need a copy-constructor on the calling site to keep it (not a reference).

Therefor this will not work in my opinion:


GraphicsFactory::initRenderEngine(GraphicsFactory::createInitiationSettings(args));
What happens if you change your code to this:


IInitiationSettings& GraphicsFactory::createInitiationSettings(const char* title, bool vsync, bool fullscreen, int width, int height){
#ifdef USE_OXYGINE_RENDERING
	return *(new OxygineInitiationSettings(title, vsync, fullscreen, width, height));
#endif
}


Certainly true, but you do not need to provide a copy-constructor (actually, in most cases it is harmful to do one by hand) since the compiler will generate one for you (provided all members can be copied).
rave3d
rave3d

The returning of the reference to an temporary object in createInitiationSettings should be the problem,

Solution was posted above. Im still not sure if the cast the opener wrote will work, and also the result of a dynamic_cast should always be checked against NULL

Brain
Brain

I dont know if it was omitted from the example for berevity, but both the base class and its derived version should both have a virtual destructor:


#pragma once
class IInitiationSettings {
public:
    virtual ~IInitiationSettings();
};

If you do not have a virtual dtor in your base class and inherited version, then you cannot properly manage resource allocation and the class will not properly follow the rule of three.

Good luck with solving your problem!

Ashaman73
Ashaman73

Certainly true, but you do not need to provide a copy-constructor (actually, in most cases it is harmful to do one by hand) since the compiler will generate one for you (provided all members can be copied).

I just wanted to point out, that a missing object to which the temporary data could be copied to, will result in trouble. E.g. by returning a temporary object which will be passed to a function call using references. He although returns only a reference to the "interface", so a copy-constructor wouldn't work without casting, thought an overwritten assignment operator using RTTI could construct a valid copy... (would this be really possible,hmmm). But, please, don't do this, this would be really evil.

BitMaster
BitMaster
Yeah, I think I quoted that too lazily to make sufficient sense.

Still, mentioning the perils of over enthusiastically writing your own copy constructors in a thread by someone obviously very fresh in C++ is not a complete waste of time...
TheAngryPlatypus
TheAngryPlatypus

Just a thought about the design: Those InitiationSettings seem to be just a collection of data that is used to initialize the graphics system. And which graphics system is used will be decided at compile time. Your solution uses run-time-polymorphism combined with up-casting and preprocessor switches. I think the run-time switching part is not necessary here, as is the polymorphic class for the init data.

I thought of something like this:


struct InitiationSettings
{
   std::string title;
   int width;
   int height;
   bool vsync;
   bool fullscreen;
};

void GraphicsFactory::init(const InitiationSettings& settings)
{
#ifdef USE_OXYGINE_RENDERING
   oxygine::core::init_desc oxygine_desc;

   //copy over settings to oxygine_desc
   
   oxygine::core::init(&oxygine_desc);
#endif
}

But you may have other reasons to use your version, so its just some input, because i don't like putting data into abstract classes :)

Flyverse
Flyverse

@Ashaman73 Thanks! I thought a reference is the same as a pointer, just without all the weird syntax. I guess I did not understand what a reference truly is...

Casting works now =)

Edit: @simber WOW, that is a good solution!

Got another error though, also with the abstraction of the render-engine...

Random question.. Is it worth it to add this kind of abstraction layer between the graphics engine and the game? It's kind of annoying to have to cast everything, etc...

rozz666
rozz666




It's kind of annoying to have to cast everything, etc...

If you need to cast, then you should review your design. The point of an abstraction not to depend on concrete types.

E.g. your GraphicsFactory::init accepts and interface, but inside you assume it's a specific class. What if I derive another class from IInitiationSettings? init will probably crash, even though init declaration told me I can pass anything that's an IInitiationSettings.

Of course, IInitiationSettings should be a data object, but this has already been address in other posts.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.