I have a UIComponent class. Window, Button and Text inherit from it. UIComponent has a recursive structure in that it stores a std::vector<std::unique_ptr<UIComponent>> contents;. I am trying to write a function that recursively that goes through contents and finds the given UIComponent.
In my original code, I use a hash-table, but the following code very closely resembles what the original looks like (sorry if it's a bit long – I couldn't make it any shorter):
#include <vector>
#include <string>
#include <memory>
#include <iostream>
class UIComponent
{
public:
std::string name = "";
UIComponent() = default;
std::vector<std::unique_ptr<UIComponent>> contents;
UIComponent(const UIComponent& other) noexcept
{
for (auto& otherContent : other.contents)
{
contents.push_back(
std::make_unique<UIComponent>(*otherContent)//recursive call
);
}
}
template <
template <class> class Component,
class Fx
>
void Attach(const Component<Fx>& comp, std::string name)
{
contents.push_back(
std::move(std::make_unique<Component<Fx>>(comp))
);
contents.back()->name = name;
}
//Should search all the components and return one that matches
//the argument
UIComponent* GetComponent(const std::string& name)
{
static UIComponent* pOut = nullptr;
for (size_t i = 0; i < contents.size(); i )
{
if (name == contents[i]->name)
{
pOut = contents[i].get();
return pOut;
}
}
if (pOut != nullptr)
{
return pOut;
}
for (auto& content : contents)
{
content->GetComponent(name);
}
return pOut;
}
};
template < class Fx >
class Window : public UIComponent
{
public:
Window() = default;
};
template < class Fx >
class Button : public UIComponent
{
public:
Button() = default;
};
template < class Fx >
class Text : public UIComponent
{
public:
Text() = default;
};
Window<void(void)> window;
Button<void(void)> button;
Text<void(void)> text;
int main()
{
window.Attach<Button, void(void)>(button, "button");
button.Attach<Text, void(void)>(text, "text");
auto b = window.GetComponent("button");
if (b != nullptr)
{
std::cout << b->name << " : " << typeid(*b).name() << '\n';
}
auto t = window.GetComponent("text");
if (t != nullptr)
{
std::cout << t->name << " : " << typeid(*t).name() << '\n';
}
}
Actual Output:
button : class UIComponent
button : class UIComponent
Expected output:
button : class Button<void(void)>
text : Text<void(void)>
The function I am struggling with is UIComponent* GetComponent(const std::string& name).
It always returns a UIComponent*. I am not sure how to make it so that tt returns the correct child class type. I am also not sure about the logic but that might not be strictly related to the question.
CodePudding user response:
For the polymorphic behaviour you are attempting to work, you need at least one of the base class member functions to be virtual.
In your code, simply adding that specifier to the GetComponent member function will do the trick; like this:
virtual UIComponent* GetComponent(const std::string& name)
{
// ... all other code as in the original.
With this simple edit, the output I see when I run your code is then1:
button : class Button<void __cdecl(void)>
button : class Button<void __cdecl(void)>
So, although you'll then need to do some 'tidying up' of the returned class names, you will at least get pointers that reflect the actual run-time objects.
But note: as you now have a virtual member function, you really ought to also make the destructor(s) virtual, as well: When to use virtual destructors? So, add this to the base class definition:
virtual ~UIComponent() = default;
1 Edit: Note that, in your main function, there appears to be a conflict between the window and button objects, in terms of which one the "Text" control is attached to. The object in the Attach call and in the subsequent GetComponent call should be the same (they aren't in your original code). Here is an example of the main code edited so that they are the same, as follows:
int main()
{
window.Attach<Button, void(void)>(button, "button");
button.Attach<Text, void(void)>(text, "text"); // NOTE: You are attaching to the button ...
auto b = window.GetComponent("button");
if (b != nullptr) {
std::cout << b->name << " : " << typeid(*b).name() << '\n';
}
auto t = button.GetComponent("text"); // NOTE: Using "button.GetComponent"
if (t != nullptr) {
std::cout << t->name << " : " << typeid(*t).name() << '\n';
}
}
The output is now (as expected):
button : class Button<void __cdecl(void)>
text : class Text<void __cdecl(void)>
