Sign in to follow this  
realh

Is C++ RTTI good or bad?

Recommended Posts

I've been aware for a while that many programmers feel that exceptions should be avoided as much as possible in C++, but today I read something that suggested RTTI has its downsides too. Most tutorials say that you should always prefer RTTI casting syntax to C-style, but can it actually cause bugs etc? Or is it just a minor performance issue?

Share this post


Link to post
Share on other sites

RTTI can have performance costs within your code, but depending on where and when you use it within your game, it might not be noticable and might be the "right tool" for the job depending on what you want to do.

 

There are many ways of identifying the type of a variable at runtime (e.g. having a "GetType()" virtual function that returns an enum value, etc) without having to resort to RTTI but sometimes they can just seem like kludges.

 

How and where do you plan to use RTTI, or were you just curious?

Share this post


Link to post
Share on other sites

I was referring more to using the *_cast<> API than explicitly checking types myself. I don't think I need to worry about performance, because the sort of games I'm writing aren't CPU-hungry. I chose C++ (over Java mainly) because of the portability and simple deployment etc rather than for performance.

 

The sort of situation where I would use dynamic_cast is where I'm using abstract classes for portability. For example I might have an abstract class called Image and a GlContext class with a pure virtual method uploadTexture(Image *), and subclasses with implementations for SDL2 and Android (not using SDL2 for Android is another story). In the Android build all Image objects would be AndroidImage and GlContext would always be AndroidGlContext. The implementation of uploadTexture needs access to members of AndroidImage which aren't available in the abstract base, so it has to cast. Seeing as each build can only have one type of subclass perhaps I'd be better off doing away with the asbtract base and have two (or more) different versions of Image and of GlContext. But I thought using OOP this way made sense.

 

There's another situation where I've recently used dynamic_cast a lot, which is harder to explain, it's for automatic creation of a dungeon. To enable the algorithm which makes sure all rooms are reachable (ie have at least one door to another room which has at least one other door to another and so on) each room has two vectors of its neighbours (rooms it shares walls with), those with mutual doors and those without. But there are also groups of rooms I call regions, and there should only be one door (a special door for which you have to find a key) between any two connected regions. This makes the connection algorithm pretty much identical for both rooms and regions so I've implemented it in a base class Connectable, with derived classes Room and Region. The vectors of neighbours are of type std::vector<Connectable *>. However, both Room and Region also need to do other Room-specific and Region-specific things with their neighbours, and it's better to reuse the vectors available in the base class and cast the members from Connectable to Room or Region as appropriate than to maintain separate vectors of the subclass. I'm sure there are other ways I could do this which avoid dynamic_cast, but I think they might lead to unpleasant things like cluttering up the classes with trivial virtual functions or using complicated templates.

Edited by realh

Share this post


Link to post
Share on other sites
The main problem most people have with C++'s RTTI is that it is very complicated to account for all the weird things you can do in the C++ type system. As most people do not need the additional complexity, they will frequently opt to making a much simpler RTTI system to save space and speed things up.

As others have pointed out - relying on type information at runtime is generally not preferred - though sometimes you do need to do it.

The sort of situation where I would use dynamic_cast is where I'm using abstract classes for portability. For example I might have an abstract class called Image and a GlContext class with a pure virtual method uploadImage(Image *), and subclasses with implementations for SDL2 and Android (not using SDL2 for Android is another story). In the Android build all Image objects would be DroidImage and GlContext would always be AndroidGlContext. The implementation of uploadImage needs access to members of AndroidImage which aren't available in the abstract base, so it has to cast. Seeing as each build can only have one type of subclass perhaps I'd be better off doing away with the asbtract base and have two (or more) different versions of Image and of GlContext. But I thought using OOP this way made sense.


In this case, you're assured that what you are getting from your caller is the right type, so static_cast instead of dynamic_cast. If you're really paranoid, you can make your own safe_cast that compares the result of a GetType() virtual call against a public const static member variable of the class that denotes its type.

(Example written in forum - may not be compilable)
class Interface
{
public:
  virtual int GetType() const = 0;
};

class Derived: public Interface
{
public:
  // number doesn't matter, as long as it is unique - some code libraries will use a giant enum
  static const int MyType = 1;

  virtual int GetType() const overrde {return MyType;}
};

template<typename T>
T* safe_cast_interface(Interface* aPointer)
{
  if (aPointer->GetType() == T::MyType)
    return static_cast<T*>(aPointer);
  return nullptr;
}

There's another situation where I've recently used dynamic_cast a lot, which is harder to explain, it's for automatic creation of a dungeon. To enable the algorithm which makes sure all rooms are reachable (ie have at least one door to another room which has at least one other door to another and so on) each room has two vectors of its neighbours (rooms it shares walls with), those with mutual doors and those without. But there are also groups of rooms I call regions, and there should only be one door (a special door for which you have to find a key) between any two connected regions. This makes the connection algorithm pretty much identical for both rooms and regions so I've implemented it in a base class Connectable, with derived classes Room and Region. The vectors of neighbours are of type std::vector<Connectable *>. However, both Room and Region also need to do other Room-specific and Region-specific things with their neighbours, and it's better to reuse the vectors available in the base class and cast the members from Connectable to Rom or Region as appropriate than to maintain separate vectors of the subclass. I'm sure there are other ways I could do this which avoid dynamic_cast, but I think they might lead to unpleasant things like cluttering up the classes with trivial virtual functions or using complicated templates.


Not sure I follow exactly what you're going for here - but the above mentioned "safe_cast" that you make yourself using virtual calls and static member variables should be able to do this too.

Share this post


Link to post
Share on other sites

Most compilers have implemented dynamic cast in a way that moving to the root class is fast, but moving to any other layer in the hierarchy is slow. 

 

If you are testing equality or testing against the base, or if you are jumping to the root all is well.  That's good because that's the most common case in well-written code.

 

If you are jumping to leaf nodes or jumping to intermediate levels in the hierarchy, probably your design is wrong. You are supposed to specialize functionality within a subclass by overriding behavior, not write code that must identify the subclass and then specialize externally. That's the opposite of the correct approach.  

 

As an example, if you are writing code that effectively tests if(dynamic_cast<foo>) { foo variant} else if (dynamic_cast<bar>) {bar variant} else if(dyanmic_cast<baz>) {baz variant}, that's a bad implementation.  Instead just run a function that works on all objects, since that's how inheritance is designed.

 

There is a cost in additional executable size for RTTI information, but it is small and not an issue on the PC.

Share this post


Link to post
Share on other sites


In this case, you're assured that what you are getting from your caller is the right type, so static_cast instead of dynamic_cast. If you're really paranoid, you can make your own safe_cast that compares the result of a GetType() virtual call against a public const static member variable of the class that denotes its type.

I see. I misunderstood the difference between dynamic_cast and static_cast and didn't realise static could go in either direction in the hierarchy. You're right, static_cast would definitely be more appropriate for my first scenario. I would probably stick with dynamic_cast for the dungeon thing though, at least until I'm sure it's debugged. I don't think the problems with dynamic_cast are significant enough in my project(s) to justify writing my own replacement, but writing a wrapper might be a good way to easily switch between dynamic_cast and static_cast for debug and release builds.

Share this post


Link to post
Share on other sites

In this case, you're assured that what you are getting from your caller is the right type, so static_cast instead of dynamic_cast. If you're really paranoid, you can make your own safe_cast that compares the result of a GetType() virtual call against a public const static member variable of the class that denotes its type.

I see. I misunderstood the difference between dynamic_cast and static_cast and didn't realise static could go in either direction in the hierarchy. You're right, static_cast would definitely be more appropriate for my first scenario. I would probably stick with dynamic_cast for the dungeon thing though, at least until I'm sure it's debugged. I don't think the problems with dynamic_cast are significant enough in my project(s) to justify writing my own replacement, but writing a wrapper might be a good way to easily switch between dynamic_cast and static_cast for debug and release builds.


Another safety net trick you can use is to use dynamic_cast in debug, and static_cast in release. That way you can catch bugs in debug, and you get speed in release (with the risk of trashing memory if you do a bad cast - but that's what your debug is for).
 
#ifdef CHECK_CASTS // define this when you want to ensure your casts are correct

template<typename T, typename U>
T* checked_cast(U* aPointer)
{
  T* retVal = nullptr;
  if (aPointer != nullptr)
  {
    retVal = dynamic_cast<T*>(aPointer);
    ASSERT(retVal != nullptr);
  }
  return retVal;
}

#else

template<typename T, typename U>
T* checked_cast(U* aPointer)
{
  return static_cast<T*>(aPointer);
}

#endif
Edited by SmkViper

Share this post


Link to post
Share on other sites


The sort of situation where I would use dynamic_cast is where I'm using abstract classes for portability. For example I might have an abstract class called Image and a GlContext class with a pure virtual method uploadImage(Image *), and subclasses with implementations for SDL2 and Android (not using SDL2 for Android is another story). In the Android build all Image objects would be DroidImage and GlContext would always be AndroidGlContext. The implementation of uploadImage needs access to members of AndroidImage which aren't available in the abstract base, so it has to cast.
You don't need casting, because you don't actually need access to AndroidImage internals: you need a more complete separation between the generic and Android-specific parts of your code. Only Android-specific classes should be allowed to have variables with Android-specific types, while generic code like an uploadImage() function should be restricted to using Image, GIContext, etc. You probably need to add something to the abstract interfaces like Image.


I've implemented it in a base class Connectable, with derived classes Room and Region. The vectors of neighbours are of type std::vector. However, both Room and Region also need to do other Room-specific and Region-specific things with their neighbours, and it's better to reuse the vectors available in the base class and cast the members from Connectable to Rom or Region as appropriate than to maintain separate vectors of the subclass.

This is just a bad design; a Connectable interface doesn't make sense, because you are always connecting regions, which are aggregates of rooms, and some regions are allowed more than one outgoing door.

Share this post


Link to post
Share on other sites

 


The sort of situation where I would use dynamic_cast is where I'm using abstract classes for portability. For example I might have an abstract class called Image and a GlContext class with a pure virtual method uploadImage(Image *), and subclasses with implementations for SDL2 and Android (not using SDL2 for Android is another story). In the Android build all Image objects would be DroidImage and GlContext would always be AndroidGlContext. The implementation of uploadImage needs access to members of AndroidImage which aren't available in the abstract base, so it has to cast.
You don't need casting, because you don't actually need access to AndroidImage internals: you need a more complete separation between the generic and Android-specific parts of your code. Only Android-specific classes should be allowed to have variables with Android-specific types, while generic code like an uploadImage() function should be restricted to using Image, GIContext, etc. You probably need to add something to the abstract interfaces like Image.

uploadTexture() needs an implementation and that does need access to implementation-specific details of Image. Exposing those implementation details in the base class would surely be worse than what I'm doing. Yes, I could add a method to Image that returns data in a defined format suitable for GlContext, but then I could be missing out on a nice convenient function that uploads an Android bitmap to the GPU (the NDK doesn't have such a function, but if I was mixing Java and C++ I could do it that way). And what if I wanted to write a DirectX implementation too? Apart from GlContext being badly named then, it would probably require Image to supply it with a different format.

Edited by realh

Share this post


Link to post
Share on other sites

 


I've implemented it in a base class Connectable, with derived classes Room and Region. The vectors of neighbours are of type std::vector. However, both Room and Region also need to do other Room-specific and Region-specific things with their neighbours, and it's better to reuse the vectors available in the base class and cast the members from Connectable to Rom or Region as appropriate than to maintain separate vectors of the subclass.

This is just a bad design; a Connectable interface doesn't make sense, because you are always connecting regions, which are aggregates of rooms, and some regions are allowed more than one outgoing door.

 

You're wrong, I can't really take advantage of Regions containing Rooms when deciding which to connect together. There should be no more than one connection between any two regions and if I just connect the entire dungeon's Rooms there's no easy way to control that. Instead it's better to treat each Region's set of Rooms as an isolated group (not allowing connections between Regions when connecting Rooms) and connect the Regions separately. That way I have two different types of object that have something in common: a group of them must be connected in a way that the entire group can be traversed with no more than one connection between any pair of neighbours. That commonality is expressed in the Connectable base class.

Share this post


Link to post
Share on other sites

uploadImage() needs an implementation and that does need access to implementation-specific details of Image. Exposing those implementation details in the base class would surely be worse than what I'm doing. Yes, I could add a method to Image that returns data in a defined format suitable for GlContext, but then I could be missing out on a nice convenient function that uploads an Android bitmap to the GPU (the NDK doesn't have such a function, but if I was mixing Java and C++ I could do it that way). And what if I wanted to write a DirectX implementation too? Apart from GlContext being badly named then, it would probably require Image to supply it with a different format.

Looks like a premature specialization to me.

Think about it. Are you SERIOUSLY going to be mixing the Android and Windows code bases like that, where you need both an Android GlContext and a Windows Direct3D implementation? Really?!

Add the functionality you need to the base class and have it do nothing on the unspecialized versions. No dynamic casting required. Generally if you follow SOLID principles, especially dependency inversion, the problem vanishes. Since users of the class can only use the base interface and never use a concrete class, so the only cast needed is to the root of the hierarchy. And as discussed in this thread with various links, the special case of converting to the root base class is essentially free.

Share this post


Link to post
Share on other sites

c-style casting is the fastest, because it's just done at compile time. If you c-style cast a pointer of a base class to a pointer of an object derived class that it is not, you'll just start to get crashes somewhere down the line when trying to access attributes it doesn't have or something.

 

dynamic_cast probably checks if virtual tables are the same or something. Don't know, seems the smartest way imo tho.

Edited by ProgrammerDX

Share this post


Link to post
Share on other sites

dynamic_cast probably checks if virtual tables are the same or something. Don't know, seems the smartest way imo tho.

 

Thats certainly not possible, consider the following case:

class Base
{
	virtual ~Base(void) = default;
}

class Child :
	public Base
{
}

class Child2 :
	public Child
{
}

Base* pBase = new Child2();

Child* pChild = dynamic_cast<Child*>(pBase);

Even though the instance stored in "pBase" is convertible to Child, it is actually of type Child2, and thus has a different vtable than an instance of Child has. Since the compiler can't know if any further derivates of any class exists (think DLLs etc...), I highly doubt that any compiler can do vtable compares for dynamic casting, not even as some sort of global optimization.

 

On topic, I actually agree that dynamic casting is usually sort of a code smell, and often not required in limited/completely user controller code. Some of the big engines like Unreal 4 and their games make heavy use of casting dynamically though, in the user front-end at least. This is ie. required to cast to a custom type of player controller that is assign to a game actor via the already existing functions. If the user now wants to call custom functions on this controller, they have to aquire it from the actor, and cast it to their own type. However, in case of Unreal, this is not handled via dynamic_cast, but a custom type system, which is probably implemented more effectively. So RTTI definately has its uses, but those are rather rare.

Edited by Juliean

Share this post


Link to post
Share on other sites
One of the ways that dynamic_cast is a code smell is when you're using it to facilitate some kind of run-time decision that realm should be a compile-time decision.

So, for example, you don't like have a single binary that could be running on Windows or in Android -- you know which platform that binary targets at the time you compile it (which, if your experience comes from Java, is a new concept to you) so it seems silly to do run-time decision making around platform support, doesn't it?

When you need this kind if compile-time polymorphism you usually want to reach for templates, or sometimes conditional compilation (#ifdef PLATFORM blocks). Compile-time polymorphism doesn't rely on inheritance to work, but instead relys on duck-typing (another new concept if Java is your background) "If it walks like a duck and talks like a duck, its a duck) -- basically, any template parameter satisfies the template as long as it can satisfy the calls made again it, but such template parameters need not be related (in practice, its a bit more subtle than that, because such template arguments must also maintain any assumptions built into the template.)

Templates are static constructs and so have no overhead.

Share this post


Link to post
Share on other sites

Most dynamic_cast implementations I'm aware of do the moral equivalent of putting a new virtual function in the vtable that accepts a string identifier for the type to cast to. The virtual function loops through a string table that contains all the type identifiers it can be legally cast to and an offset for each type and does a strcmp() against those strings. Once it finds a matching type identifier it applies the offset to the this pointer and returns the new address. Since most inheritance hierarchies only contain a few levels, this is generally a linear traversal of the table. Usually the string identifier of concrete type comes first in the string table, followed by classes that it immediately inherit from, followed by classes that those immediately inherit from, and so on. This reflects that fact that if you have a derived class pointer you don't need to dynamic_cast to get a base class pointer, so most dynamic casts are used to find more specialized types. Of course, there are additional details like returning the correct errors in the presence of ambiguous base class casts, handling casts to pointer and reference differently and so on, but that's the basic idea.

 

One of the fun parts about implementing RTTI in the presence of dynamic libraries, is not only can they introduce new types, but each dynamic library can have a separate copy of each vtable for each class in a hierarchy. So a program that loads two DLLs may have three copies of the vtable for the same base class. Objects of the same type allocated in a different library could have different vptrs, which rules out using vptr identity to do much of anything. 

Share this post


Link to post
Share on other sites

Let's say instead of using libpng I wanted to use helper functions akin to what Android provides in Java. The helper function is associated with the OpenGL ES context so it belongs in an implementation of my GlContext class (let's call it RenderContext instead, in case I want to change to DirectX or Vulkan or something later), not in Image, which I want to keep decoupled from the rendering context. However, this helper function only works if it can get an AndroidBitmap from Image. On the other hand I need (an) alternative implementation(s) for Windows, Linux etc, but I want other parts of my code to be able to use a RenderContext and Images without having to know about platform-specific or implementation details.

 

I thought a sensible way to achieve that would be to have the platform-agnostic parts use abstract base classes RenderContext and Image, with the implementations in respective subclasses. The uploadTexture() method is part of the abstract interface so its parameter has to be the abstract base Image too, and the AndroidGlContext implementation of RenderContext has to cast Image to AndroidImage (which has a getAndroidBitmap() method).

 

An alternative way would be to make RenderContext and Image concrete, with their implementations included. Instead of deriving from them I would write two or more versions of each with the same class name, or using typedef, and use the compiler/build system to switch in the relevant implementation. That has the disadvantage of exposing implementation details throughout the application. Are you all saying that's preferable to using abstract bases to provide an interface and having to use that cast (but at least now I know it can be static, not dynamic)?

Edited by realh

Share this post


Link to post
Share on other sites
You will not be running anything near to the same library on Android, Windows, and Linux.

The rare programs that do such things are not written by a single person. Cross platform libraries like that are developed by big companies by people who have a lot of experience with cross-platform libraries already.

The libraries are not dynamically swapped out, they are usually completely separate in the build system. One platform uses one build tree, another platform uses another build tree, and they just happen to have a common branch within the build tree. Creating those single-platform branches of the tree is done by another team of individuals.

Make the game for one platform, and don't worry so much about future platforms that you want to someday port to. YAGNI is a great mantra. Sure, someday you might port your program to other operating systems. But right now you are developing for one platform, so you aren't going to need it.

Share this post


Link to post
Share on other sites

Thought I'd mention... A popular solution to getting rid of dynamic_cast is using the Visitor pattern, but Stroustrup hates that pattern. smile.png  Him and his students have been looking into alternative methods. (https://isocpp.org/blog/2013/02/open-and-efficient-type-switch-for-c-solodkyy-dos-reis-and-stroustrup)

Share this post


Link to post
Share on other sites

frob, I really disagree with you about tying myself to one platform from day one. Using SDL2 and the bits that OpenGL 2 and OpenGL ES 2 have in common, I can easily target multiple platforms with one code base and I've got ideas for games that are suitable for both desktops and mobiles. I've written prototype frameworks and tested them successfully on Linux (including Raspberry Pi), Windows, OS X, and with mixed results on Android. So I wrote another branch of my framework using the Android NDK API and no SDL. It wasn't very hard because I made up my mind from the beginning not to put all my eggs in SDL and avoided exposing its API in the app-facing part of my framework. It's not something I'm thinking I might want to do in the future, it's something I've already done successfully.

 

I think I got the idea for using abstract classes for the API and subclasses for different implementations from Mario Zechner's book, and he's no idiot. But that was Java (where interfaces are a big deal) so I wondered whether C++'s greater flexibility and very different infrastructure cross-platform-wise offered a more elegant pattern. I'm getting the impression most people misunderstood the problem I was trying to solve, just saw "cast" and jumped straight to the conclusion I must be doing something wrong and I'm a n00b.

 

Of course I realise that the implementations for each platform have to be built separately.

Share this post


Link to post
Share on other sites

One of the ways that dynamic_cast is a code smell is when you're using it to facilitate some kind of run-time decision that realm should be a compile-time decision.

So, for example, you don't like have a single binary that could be running on Windows or in Android -- you know which platform that binary targets at the time you compile it (which, if your experience comes from Java, is a new concept to you) so it seems silly to do run-time decision making around platform support, doesn't it?


I'm not trying to use casting for a run-time decision though. In the Android version the GlContext is always an AndroidGlContext and all Images are AndroidImages. But I want other parts of the program to be able to use GlContext and Image without caring whether they're the Android implementations or SDL or whatever, and I chose to express that by using abstract classes as API and subclasses for the implementation. That does look very Java-esque, true, but Java isn't my primary background.
 

When you need this kind if compile-time polymorphism you usually want to reach for templates, or sometimes conditional compilation (#ifdef PLATFORM blocks).

 

Right, #ifdef is probably the only really practical alternative I could think of too, but one disadvantage of that is exposing implementation-specific parts to the public API. Best make them private and use friend where necessary I suppose. OTOH it has the advantage of not requiring virtual functions, so I think I'll regard this as the better option overall.

 

Compile-time polymorphism doesn't rely on inheritance to work, but instead relys on duck-typing (another new concept if Java is your background) "If it walks like a duck and talks like a duck, its a duck) -- basically, any template parameter satisfies the template as long as it can satisfy the calls made again it, but such template parameters need not be related (in practice, its a bit more subtle than that, because such template arguments must also maintain any assumptions built into the template.)

Templates are static constructs and so have no overhead.

I've been writing C++ on and off for nearly 20 years, but I'm more experienced with C. I learnt Java specifically for Android; I like it a lot but decided I'd rather make my games cross-platform, so C++ is more practical. I know python too, so I have come across duck-typing as a term and recognise it in templates.

Share this post


Link to post
Share on other sites

Can you post an exact example of what you would need to downcast? I have been using a very similar concept for my graphics API where I have abstract base classes like ITexture, IMesh, IMaterial... which use each other, but I *almost* never had to downcast, and in the cases where I had to, I've gotten away with using visitor pattern. I suspect in your case this could be solved without dynamic_cast, so if you can show some concrete code example, that would be great.

Share this post


Link to post
Share on other sites
For compile-time polymorphism of per-platform implementations of clean public interfaces, I use this variation of the PIMPL pattern (where the this pointer is the PIMPL):
// GpuContext.h -- public/user visible code
class GpuContext : NoConstruct
{
  static GpuContext* Create(...);
  static void Release(GpuContext*);

  void Frobnicate();
};
 

// D3D11Context.cpp -- conditionally compiled
class D3D11Context : NonCopyable
{
  ...
  ID3D11DeviceContext* context;
};
void GpuContext::Frobnicate()
{
  D3D11Context& self = *(D3D11Context*)this;
  self.context->Frobnicate();
}
GpuContext* GpuContext::Create(...) { return (GpuContext*)new D3D11Context(...); };
void GpuContext::Release(GpuContext* ctx) { delete (D3D11Context*)ctx; }
 

// OpenGLContext.cpp -- conditionally compiled
class OpenGLContext : NonCopyable
{
  ...
};
void GpuContext::Frobnicate()
{
  OpenGLContext& self = *(OpenGLContext*)this;
  glFrobnicate();
}
GpuContext* GpuContext::Create(...) { return (GpuContext*)new OpenGLContext(...); };
void GpuContext::Release(GpuContext* ctx) { delete (OpenGLContext*)ctx; }
Edited by Hodgman

Share this post


Link to post
Share on other sites

@Juliean I don't have a concrete example in C++ handy at the moment, but I can show you the Java the pattern is based on. It's at https://code.google.com/p/bombz/source/browse?repo=java2. Go to app/src/main/java/uk/co/realh/hgame/ and look at the Image interface and RenderContext class (in particular the latter's uploadTexture() abstract method). These are the types referred to throughout the game code. Their implementations are in app/src/main/java/uk/co/realh/hgame/gles1/android/AndroidGles1RenderContext.java and app/src/main/java/uk/co/realh/hgame/android/AndroidImage.java. AndroidImage is implemented with Android's Bitmap class. AndroidGles1RenderContext implements uploadTexture() using a utility function provided by Android which takes a GL10 context and a Bitmap as parameters. uploadTexture() is an implementation of an abstract function which takes Image as a parameter, so at some point there has to be a cast to AndroidImage to access the Bitmap, which is done on line 93 of AndroidGles1RenderContext.java.

 

I don't see how the Visitor pattern would provide a more elegant solution at all. My best option seems to be to move uploadTexture() to Image, but then I change from one undesirable detail (the cast) to two: (a) I don't think this method conceptually belongs in Image, and I'd either (b1) have to cast an abstract GlContext parameter to an implementation to get the GL10, only moving the problem instead of solving it; or (b2) get the GL10 via a global singleton.

 

The option of getting rid of the abstract bases/interfaces is also possible here, but without C++'s preprocessor I'd have to manually change the import statements in every file that uses those classes whenever I wanted to switch build target.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this