Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent code heavily relying on polymorphism from being littered with `make_shared` all over the place?

I am writing a UI framework for a microcontroller, in which I want to do a hierarchical menu system.

For that in particular I have the following classes:

class MenuNode {
public:
    MenuNode(const std::string t, const UI::Image* img): title{t}, icon{img} {}
    virtual ~MenuNode() = default;

    const std::string title;
    const UI::Image * icon;
    virtual void execute(MenuNavigator * host) const { ESP_LOGE("MenuNode", "Did you forget to override execute()?"); }
    virtual bool is_submenu() const { return false; }
};


class ListMenuNode: public MenuPresentable, public MenuNode,  public UI::ListView {
public:
    ListMenuNode(const std::string title, const std::vector<std::shared_ptr<MenuNode>>& items, const UI::Image* icon = nullptr): 
        subnodes(items),
        UI::ListView(EGRectZero, {}), MenuNode(title, icon) {
    }

    void execute(MenuNavigator * host) const override {
        host->push(std::make_shared<ListMenuNode>(*this));
    }

    void on_presented() override {
        // update frames of things after push has set our frame correctly
        ...
    }

    void on_key_pressed(VirtualKey k, MenuNavigator* host) override {
        if(k == RVK_CURS_DOWN) down();
        else if(k == RVK_CURS_UP) up();
        else if(k == RVK_CURS_ENTER) {
            subnodes[selection]->execute(host);
        }
        else if(k == RVK_CURS_LEFT) {
            host->pop();
        }
    }
protected:
    const std::vector<std::shared_ptr<MenuNode>> subnodes;
};

In here, I have to make subnodes a vector of shared_ptr<MenuNode> instead of directly MenuNode — otherwise upon selection of an item, only MenuNode::execute() ever gets called instead of the descendant class override of this method.

I am fine with that for the most part, however, this means a declaration of the menu looks like the following

ListMenuNode(
 "Settings",
    {
        std::make_shared<MenuNode>("Item 1", &icn_wifi),
        std::make_shared<ListMenuNode>(ListMenuNode("Drill Down", {
            std::make_shared<MenuNode>("Drill Item 1", &icn_cd),
            std::make_shared<MenuNode>("Drill Item 2", &icn_bt),
            std::make_shared<MenuNode>("Drill Item 3", &icn_radio),
        }, &icn_about))
    }
)

Which looks very unclean due to repetitive calls to make_shared.

Is there some way to make the pointer creation implicit, so that I can just write something like the following instead?

ListMenuNode(
 "Settings",
    {
        MenuNode("Item 1", &icn_wifi),
        ListMenuNode("Drill Down", {
            MenuNode("Drill Item 1", &icn_cd),
            MenuNode("Drill Item 2", &icn_bt),
            MenuNode("Drill Item 3", &icn_radio),
        }, &icn_about)
    }
)

One way I thought of was using #define's for each class name, but that sounds not very clean to me.

like image 711
akasaka Avatar asked Nov 17 '25 06:11

akasaka


1 Answers

Not sure it is a good idea, but, if your types are movable, you might use variadic template constructor.

using std::tuple instead of std::vector:

template <typename... Ts>
ListMenuNode(const std::string title,
             std::tuple<Ts...>&& items,
             const UI::Image* icon = nullptr) :
    subnodes(std::apply([](auto&&... args) -> std::vector<std::shared_ptr<MenuNode>>
                        {
                             return { std::make_shared<Ts>(std::move(args))... };
                         }, std::move(items)),
    UI::ListView(EGRectZero, {}),
    MenuNode(title, icon)
{
}

With call similar to

ListMenuNode(
 "Settings",
    std::tuple{
        MenuNode("Item 1", &icn_wifi),
        ListMenuNode("Drill Down", std::tuple{
            MenuNode("Drill Item 1", &icn_cd),
            MenuNode("Drill Item 2", &icn_bt),
            MenuNode("Drill Item 3", &icn_radio),
        }, &icn_about)
    }
)
like image 148
Jarod42 Avatar answered Nov 18 '25 21:11

Jarod42



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!