C++ design question
-
- DC Developer
- Posts: 9951
- https://www.artistsworkshop.eu/meble-kuchenne-na-wymiar-warszawa-gdzie-zamowic/
- Joined: Sun Dec 30, 2001 9:02 am
- Has thanked: 0
- Been thanked: 1 time
C++ design question
My current approach to this is unbelievably ugly, so presumably there's a better way...
I want to write a program in C++, which can run on multiple platforms. Certain components (video, sound, input...) are platform dependent, so I need to implement different versions of the appropriate back-end code. In other words, I need to be able to swap out the back-end classes and replace them.
I can't simply have the back-end classes defined in a single header file, with different implementations in different files. That would work if no implementation required any additional member variables, but more often than not they do.
Each group of platform specific classes are quite tightly coupled. For example, an OpenGL-specific texture object can only really interact with other OpenGL-specific objects. For example, the core renderer might be a friend of the texture class, so it can directly access platform-specific information about the texture (like a PVR pointer, or an OpenGL texture ID). The renderer needs this information, and there's no way to move it around. In this case, making a base texture class and deriving platform-specific implementations from it doesn't make any sense. There's only ever going to be one implementation for each platform, you aren't going to be able to swap between different implementations on the fly, and if you were you would have some serious issues with type safety (or lack thereof).
So that leaves me with my current implementation, which is a bloody mess. There are two separate copies of the class header (and thus, two copies of the class definition), and two completely separate implementations of each class. They share an API, but that's all. They are otherwise completely separate, any shared code or interfaces must be manually copied across, and it's generally a right pain in the backside.
So, is there any clean solution to this, or am I going to have to put up with the rather disturbing mess that I've got?
Ideally, I'd like to be able to have a single definition for a class that works across all platforms, without using too many #ifdefs (preferably none at all), and still being able to share parts of the implementation where it makes sense to do so (although there's not much that can be shared in this case).
I want to write a program in C++, which can run on multiple platforms. Certain components (video, sound, input...) are platform dependent, so I need to implement different versions of the appropriate back-end code. In other words, I need to be able to swap out the back-end classes and replace them.
I can't simply have the back-end classes defined in a single header file, with different implementations in different files. That would work if no implementation required any additional member variables, but more often than not they do.
Each group of platform specific classes are quite tightly coupled. For example, an OpenGL-specific texture object can only really interact with other OpenGL-specific objects. For example, the core renderer might be a friend of the texture class, so it can directly access platform-specific information about the texture (like a PVR pointer, or an OpenGL texture ID). The renderer needs this information, and there's no way to move it around. In this case, making a base texture class and deriving platform-specific implementations from it doesn't make any sense. There's only ever going to be one implementation for each platform, you aren't going to be able to swap between different implementations on the fly, and if you were you would have some serious issues with type safety (or lack thereof).
So that leaves me with my current implementation, which is a bloody mess. There are two separate copies of the class header (and thus, two copies of the class definition), and two completely separate implementations of each class. They share an API, but that's all. They are otherwise completely separate, any shared code or interfaces must be manually copied across, and it's generally a right pain in the backside.
So, is there any clean solution to this, or am I going to have to put up with the rather disturbing mess that I've got?
Ideally, I'd like to be able to have a single definition for a class that works across all platforms, without using too many #ifdefs (preferably none at all), and still being able to share parts of the implementation where it makes sense to do so (although there's not much that can be shared in this case).
-
- DC Developer
- Posts: 2285
- Joined: Fri Feb 21, 2003 7:37 am
- Location: Chicago, IL
- Has thanked: 0
- Been thanked: 1 time
- Contact:
I think in order to avoid the copying-functionality-between-implementations problem that you have right now, you have to go the #ifdef route. However, you can do that and still keep things clean.
Yes, you have to ifdef off all the platform specific member variabled for these classes. That's not too bad. What gets ugly is the functions where you have long blocks of code that are platform specific. I hate #idef blocks where I can't see the beginning and end of it in the same screen. You can cure lots of that with well written macros. But it's still going to be a little hairy, but better than your current method. You still get to keep a single copy of the program logic.
I think what's most important is to have the code, at compile time, be compiled as if you had written it only for that platform. I wouldn't use anything like class inheritence or purely virtual classes to accomplish it. That's great if you're switching out renderers at runtime, but not for what you're doing. Speed-wise, your current method or ifdefs is the best.
Yes, you have to ifdef off all the platform specific member variabled for these classes. That's not too bad. What gets ugly is the functions where you have long blocks of code that are platform specific. I hate #idef blocks where I can't see the beginning and end of it in the same screen. You can cure lots of that with well written macros. But it's still going to be a little hairy, but better than your current method. You still get to keep a single copy of the program logic.
I think what's most important is to have the code, at compile time, be compiled as if you had written it only for that platform. I wouldn't use anything like class inheritence or purely virtual classes to accomplish it. That's great if you're switching out renderers at runtime, but not for what you're doing. Speed-wise, your current method or ifdefs is the best.
-
- DC Developer
- Posts: 9951
- Joined: Sun Dec 30, 2001 9:02 am
- Has thanked: 0
- Been thanked: 1 time
Ah, I was afraid of that. The only alternative I could see was inheritance or virtual classes, which I really do not want to do. Aside from still having duplicate definitions sprouting out all over the place, the extra level of indirection everywhere would be un-good.
So, we're back to stuffing the header files full of #ifdefs and macros, and trying not to mess up the implementation too much. At least I can keep most of the actual code clean.
Thanks!
So, we're back to stuffing the header files full of #ifdefs and macros, and trying not to mess up the implementation too much. At least I can keep most of the actual code clean.
Thanks!
-
- Insane DCEmu
- Posts: 138
- Joined: Sun Apr 06, 2003 5:29 am
- Has thanked: 0
- Been thanked: 0
- Contact:
Interfaces combined with #ifdef statements might help?
How about defining the interface required, then create 2 seperate classes, both implementing the Interface, but with platform specific implementations of the interface.
When you need to define which class to use, an #ifdef could be used with an interface pointer to the instance.
That interface pointer could be used to call the methods of the interface implemented in the class then. and the pointer should be compatible with both implementations of the class.
sorry if I didn't make any of that clear....In a rush. Hope it helps.
How about defining the interface required, then create 2 seperate classes, both implementing the Interface, but with platform specific implementations of the interface.
When you need to define which class to use, an #ifdef could be used with an interface pointer to the instance.
That interface pointer could be used to call the methods of the interface implemented in the class then. and the pointer should be compatible with both implementations of the class.
sorry if I didn't make any of that clear....In a rush. Hope it helps.
-
- DC Developer
- Posts: 2285
- Joined: Fri Feb 21, 2003 7:37 am
- Location: Chicago, IL
- Has thanked: 0
- Been thanked: 1 time
- Contact:
That extra level of indirection will hurt performance because every function call requires a lookup in the vtable for that class. Interfaces (purely virtual classes) are great if there is more than one implementation of that interface at runtime. But, as BA said, that's not the case. He would never have the DC rendering code compiled with the DirectX rendering code, nor could he. The class to which an interface pointer will point to will never change, so it's unecessary.Vorrtexx wrote:Interfaces combined with #ifdef statements might help?
How about defining the interface required, then create 2 seperate classes, both implementing the Interface, but with platform specific implementations of the interface.
When you need to define which class to use, an #ifdef could be used with an interface pointer to the instance.
That interface pointer could be used to call the methods of the interface implemented in the class then. and the pointer should be compatible with both implementations of the class.
sorry if I didn't make any of that clear....In a rush. Hope it helps.
-
- DC Developer
- Posts: 80
- Joined: Wed Oct 17, 2001 7:44 pm
- Location: Sweden
- Has thanked: 0
- Been thanked: 1 time
Though it would most probably not be the performance bottleneck.scherzo wrote:That extra level of indirection will hurt performance because every function call requires a lookup in the vtable for that class.
Though it would give more readable code.scherzo wrote:The class to which an interface pointer will point to will never change, so it's unecessary.
I agree, the data layer should not change with the implementation, but the control-classes sometimes must implement the common interfaces differently. For the example with textures, I would give each texture an "int id;"-variable and implement the loading, setting of current active texture, disposing, etc in a singleton TextureManager-class. That class might use the "id" as a pointer to a pvr_poly_hdr_t, index into some array or an opengl texture id. It's up to the implementation of that class.BlackAura wrote:In this case, making a base texture class and deriving platform-specific implementations from it doesn't make any sense.
The same idea could be adapted to sound, 3d objects and other areas also.
-
- DC Developer
- Posts: 9951
- Joined: Sun Dec 30, 2001 9:02 am
- Has thanked: 0
- Been thanked: 1 time
I currently have a single definition of each class, with platform-specific stuff (like pointers to SDL objects, PVR pointers, handles, whatever) #ifdefed out appropriately. There are three implementation files - one for platform independent code, and one for each supported platform. Some of the classes have functions that implement OS-specific stuff, and they're called at the appropriate time from the platform independent stuff. It's still a little messy, but it's functional.
-
- DC Developer
- Posts: 968
- Joined: Tue Feb 11, 2003 4:12 pm
- Location: In a Dream
- Has thanked: 5 times
- Been thanked: 6 times
I may not have properly understood the problem, but I would suggest using templates. My suggestion:
If each of the platforms uses a unique set of variables and procedures, then you have to write the code individually for each of them. The trick is to factor out the common functions and then base your template interface using these eg
// now we can use the above interface to create renderes for different systems
The concept of a poly and a texture should be kept common as possible throughout the entire program and each backend should hack the concept to make it fit in with its native structure
// now we can construct our prog without worrying about backends
//game.hpp
// Time to select engine for specific platform
// posix_main.cpp
// windows_main.cpp
If I understood the problem, if my approach does not overestimate the capabilities of c++ templates, if I actually have experience writing real-world apps (like you do) then I wouldn't have to worry about #ifndefs et al.
If each of the platforms uses a unique set of variables and procedures, then you have to write the code individually for each of them. The trick is to factor out the common functions and then base your template interface using these eg
Code: Select all
/*
common interface for all classes. used in the main part of the program
and dictates the functions which the backend(s) should provide
*/
template<class renderer_>
class engine {
public:
engine(<common params>) {
//init _engine_renderer;
}
public:
void draw_poly(const params& p) {
// now all classes passed as a template must provide this func
_engine_renderer.draw_poly(p);
}
void texture_poly(const params& p) {
// now all classes passed as a template must provide this func
_engine_renderer.texture_poly(p);
// now all classes passed must contain this static variable
if(_engine_renderer::poly_count > 900000) {
}
//etc
}
private:
renderer_ _engine_renderer;
};
Code: Select all
class opengl_renderer {
public:
void draw_poly(const params&);
void texture_poly(const params&);
private:
//opengl-specific state
};
// now we can construct our prog without worrying about backends
//game.hpp
Code: Select all
template<class renderer_>
class game {
public:
// develop game using an abstract gfx engine
private:
engine<renderer_> _gfx_engine;
};
// posix_main.cpp
Code: Select all
#include "opengl_renderer.hpp"
#include "engine.hpp"
#include "game.hpp"
int main(int argc, char* argv[]) {
//getopt
game<opengl_renderer> my_game(params);
}
Code: Select all
#include "directx_renderer.hpp"
#include "engine.hpp"
#include "game.hpp"
int main(int argc, char* argv[]) {
//getopt
game<directx_renderer> my_game(params);
}
behold the mind
inspired by Dreamcast
inspired by Dreamcast