IGUIElement* back to derived class

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
Post Reply
LunaRebirth
Posts: 386
Joined: Sun May 11, 2014 12:13 am

IGUIElement* back to derived class

Post by LunaRebirth »

Hey guys,

I'm trying to learn what I think should be smart pointers, but I need some direction as I'm not sure I'm doing it correctly.

Suppose I have a vector of IGUIElements, and I want to set the text of the element depending on it's subclass type.

Here's how I have been doing it:

Code: Select all

 
class Element {
  public:
    std::string type;
    IGUIElement* elem;
 
    Element::Element(IGUIElement* elem, std::string type) { this->elem = elem; this->type = type; }
};
 
std::vector<Element*> elements;
 
int main()
{
  elements.push_back(new Element((IGUIElement*)guienv->addStaticText(L"Test Text", rect<s32>(0,0,10,10)), "IGUIStaticText"));
  elements.push_back(new Element((IGUIElement*)guienv->addButton(rect<s32>(0,0,10,10),0,-1,L"Test button"), "IGUIButton"));
 
  for (Element* elem : elements)
  {
    if (elem->type == "IGUIStaticText")
      ((IGUIStaticText*)elem->elem)->setText(L"New Text!");
    else if (elem->type == "IGUIButton")
      ((IGUIButton*)elem->elem)->setText(L"New Text!");
  }
 
  return 0;
}
 
Is there some way I can remove the type-checking and just do

Code: Select all

 
// …
int main()
{
  // …
  for (Element* elem : elements)
  {
    elem->elem->setText(L"New Text!");
  }
}
 
?

I need to be able to cast the object back to it's derived class, without casting for each element type like (IGUIButton*), while the vector stores it as the base class.

I believe I should be doing something with smart pointers, such as

Code: Select all

std::vector<std::unique_ptr<IGUIElement*>> elements
but I'm not sure how to cast it back and call setText.

Help would be appreciated. Thanks,
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Re: IGUIElement* back to derived class

Post by Seven »

All IGUIElemnts have a setText() function so no need to cast to anything else

//! Sets the new caption of this element.
virtual void setText(const wchar_t* text)
{
Text = text;
}

LunaRebirth wrote: Suppose I have a vector of IGUIElements, and I want to set the text of the element depending on it's subclass type.

Code: Select all

 
std::vector<IGUIElement*> elements;
 
int main()
{
  elements.push_back(guienv->addStaticText(L"Test Text", rect<s32>(0,0,10,10));
  elements.push_back(guienv->addButton(rect<s32>(0,0,10,10),0,-1,L"Test button"));
 
 for (IGUIElement* elem : elements)
  {
      elem->setText(L"New Text!");
  }
 
  return 0;
}
 
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Re: IGUIElement* back to derived class

Post by Seven »

if you want to do other than setText, you can get the IGUIElement type using
//! Returns the type of the gui element.
/** This is needed for the .NET wrapper but will be used
later for serializing and deserializing.
If you wrote your own GUIElements, you need to set the type for your element as first parameter
in the constructor of IGUIElement. For own (=unknown) elements, simply use EGUIET_ELEMENT as type */
EGUI_ELEMENT_TYPE getType() const
{
return Type;
}

for example :
IGUIElement* e = (somehow_get_pointer_to_IGUIElement_instance)
if (e)
{
if (e->getType() == EGUIET_BUTTON) { IGUIButton* button = dynamic_cast<IGUIButton*>(e); if(button) button->setPressed(true); }
if (e->getType() == EGUIET_EDITBOX) IGUIEditbox* ditbox = dynamic_cast<IGUIEditbox*>(e); if (editbox) editbox->doSomthing(); }
etc...…..
}

could use static_cast<> if you are certain it will not fail.
LunaRebirth
Posts: 386
Joined: Sun May 11, 2014 12:13 am

Re: IGUIElement* back to derived class

Post by LunaRebirth »

Yes, I want to do it for any function. setText must've been a poor example; I didn't realize it was on any element (such as IGUITable or IGUIImage).

I need to be able to tell what type an element is from it's base class, but checking several types (IGUIButton, IGUIImage, IGUITable, IGUIButton, etc.) beforehand seems very inefficient, so I wasn't sure if there was a better, more C++ way, of doing this.

It would be nice if a ternary #define could work for this, but it doesn't. Is there any way to do something similar to this?

Code: Select all

 
#define ELEM_TYPE(e) (e->getType() == EGUIET_BUTTON) ? IGUIButton : (e->getType() == EGUIET_EDITBOX) ? IGUIEditBox : IGUIImage
 
std::vector<IGUIElement*> elements;
 
int main()
{
  //...
  for (IGUIElement* elem : elements)
  {
    ((ELEM_TYPE(elem)*)elem)->doSomething();
  }
}
 
?

What I really need is a readable way of determining the element type. As I'm porting it to Lua, we can assume the function always exists since I've binded functions to element types anyways, which also means I should be able to check the type in a template argument.

There are dozens of areas where I have to do "if (elementType == ...) else if (elementType == ...) ..." and it gets really heavy to read.

Using a define, if possible, would help readability but not performance. Remember I am doing methods like this in many many many places and having to type check. So I was wondering if there was anything in C++ standard, or even Boost, that could help out.
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: IGUIElement* back to derived class

Post by CuteAlien »

Irrlicht also has a hasType function in IGUIElement, maybe that allows you to write it slightly shorter. And that also works with derived classes if implemented correct (they should return true if hasType is derived from a base-class, so for example if you derive a class from IGUIEditText then getType might return anything but hasType should also return true for EGUIET_EDITBOX).

The problem we are talking about here is runtime type information. C++ does have some way to add that, but it's a compile option (enable RTTI) and Irrlicht is not using it and also not tested if it works when you enable this. The reason it's disabled in Irrlicht (and many games in general) is that RTTI is not a zero-cost features but adds a general overhead to all classes and structs which have a virtual function table. Also the type checks used by RTTI can be more expensive than simple integer comparisons. When rtti is enabled in compiling you can use dynamic_cast which return 0 for invalid downcasts (casts to derived types). You have to recompile the engine for this (and I wouldn't recommend it).

So... case-switches or if's with hasType checks are really the solution. Don't worry about performance - one reason it's done that way is that this is the fastest solution to get type-information at runtime. Also in general when you have objects derived from IGUIElement together in a container then you should in most cases really only need the base-class functions which are always available. But I guess especially with script-languages or editors involved there can be situations where this is not the case.

As a side-node - smart-pointers are about object lifetime management (like a class around a pointer which does the reference counting so you never have to manually use delete on pointers). Like shared_ptr, weak_ptr and unique_ptr in c++.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
LunaRebirth
Posts: 386
Joined: Sun May 11, 2014 12:13 am

Re: IGUIElement* back to derived class

Post by LunaRebirth »

Thanks for the info. Looks like I'll just keep doing type-checking then
Post Reply