1

I'm currently trying to make a simple program with UI where a user can add, select and remove different objects inherited from an abstract common interface with a mouse.

What I plan to do is use a set<unique_ptr<Concrete_class*>> to store collections separately for each type of all the objects created and store one currently selected object in a unique_ptr<Abstract_class*>.

The problem is that if I want to delete the selected object there is no elegant way to do it: either I have to use a combination of .get(), dynamic casts and if/else chains to ensure the object is removed from the right set or store the selected object in a variant<Concrete_class1*, Concrete_class2*, ...> which makes using polymorphism pointless. It's also impossible to store all the objects in one set since I plan to use their specific functions.

Is there a better way to approach this problem? I use dear imgui+sfml if that's important.

Robert Harvey
201k55 gold badges469 silver badges682 bronze badges
asked Oct 12, 2020 at 15:46
4
  • It's also impossible to store all the objects in one set since I plan to use their specific functions. -- Why does this follow? If you know the type of the object, just upcast it to get to the functions you want. Commented Oct 12, 2020 at 16:10
  • @RobertHarvey Isn't doing that bad code practice since nothing but the container names explicitly tells that this conversion is legal? Commented Oct 12, 2020 at 16:18
  • If it is a bad practice, it's a bad practice specifically immortalized in the Factory Method Pattern. See en.wikipedia.org/wiki/Factory_method_pattern#C# for an example. Commented Oct 12, 2020 at 16:20
  • 1
    Why don't you just use polymorphism and collect all the widgets together into one set<unique_ptr<Abstract_class>>? (not Abstract_class* by the way) Commented Oct 12, 2020 at 16:30

3 Answers 3

0

You've got two levels of indirection too many in your collections, and you really shouldn't have two std::unique_ptr's pointing to the same object.

You can store how to erase the element when you select it.

class Container {
 std::set<ConcreteClass1> concrete1;
 std::set<ConcreteClass2> concrete2;
 AbstractClass * selected;
 std::function<void()> remover;
public:
 // Or some other way of identifying an element of concrete1
 void select(std::set<ConcreteClass1>::iterator it) {
 selected = &*it;
 remover = [=](){ concrete1.erase(it); selected = nullptr; };
 }
 // Or some other way of identifying an element of concrete2
 void select(std::set<ConcreteClass2>::iterator it) {
 selected = &*it;
 remover = [=](){ concrete2.erase(it); selected = nullptr; };
 }
 void removeSelected() {
 remover();
 }
};
answered Oct 12, 2020 at 17:06
0

You shouldn't have a set of ConcreteClass, you should use a set of AbstractClass and use the heap (new/delete).

answered Nov 11, 2020 at 20:18
0

First off, keep in mind that GUI structure (e.g., for rendering) generally doesn't match the logical structure of what the program itself does.

For example, if you have a list of items in your GUI, you presumably also have some logical component in your program that gives that list some sort of semantics within the program. The reason that the user adds an item to the list will almost certainly differ from the reason that the GUI framework needs to know that the item belongs to the list.

The point is that you will pretty much never have a program where the inheritance and nesting of GUI components reflects the semantics of the corresponding program objects. The GUI should be an expression of information rather than the information.

One approach is to have each renderable object own its GUI representation, rather than having the GUI object contain the program semantics. (Also see Composition over inheritance.)

For example, suppose you wanted to implement a list of filenames, where the user can create, open, or delete a file.

  • class FilenameList:
    • Contains a pointer to its GUI widget that can be passed to its parent for rendering.
    • Contains a list of Filename.
    • The create, open, and delete operations perform actions on both the GUI representation and the list of Filename.
    • GUI event handlers are set to delegate create, open and delete actions to the FilenameList object.

With such a representation, it's irrelevant to the GUI framework what the semantics of the program objects are; it just needs to be able to delegate events and call handlers. Then the owner of the GUI component registers itself to handle specific events, e.g., a "delete" button click.

Thus, you end up dealing with "regular" program objects that are able to handle their own GUI representations, rather than structuring the program directly with GUI representations.

answered Nov 18, 2020 at 20:28

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.