Search code examples
c++data-structuresstructnestedhierarchical

General advice on hierarchical structs


Edit: I will leave the first part of my question as I originally posted it, though I can now see that I phrased the questions at the end poorly. I always take everything extremely literally, and it sometimes surprises me that others don't. I know that's a shortcoming of my own. I have attempted to rephrase the questions to hopefully better communicate what kind of answers I was actually looking for.

Let me begin by saying that the actual implementation of this probably doesn't matter much since I'm working on a very small-scale project but I've always valued theory highly so that I develop the right habits right from the beginning, and as such I tend to get stuck in places where it doesn't really matter. Let's pretend that it matters, though. I'm eager to know the pros and cons of the different options that I have in this situation.

I'm working on a program where you can customize the environment to your liking. The settings for one such environment is called a profile. You can switch between several profiles, and only one profile may be active at one time.

It is possible that I may want to update the program in the future which means I could potentially try to load an outdated profile format where certain settings are missing. To make things easier on myself and to ensure backwards compatibility I have decided to make a class called ProfileManager that will load the profile formats and update them if necessary.

I have also decided to make a struct Profile that will hold all the settings, and this is where my question comes into the picture.

See the three versions I have tried out here:

struct Profile
{
    // Version one: lots of variables with closely related names
    bool window_one_open;
    bool window_one_collapsed;
    int window_one_pos_x;
    int window_one_pos_y;
    int window_one_width;
    int window_one_height;

    // Version two: named structs used to easily create multiple entries of the same format
    struct position
    {
        int x;
        int y;
    };

    struct window
    {
        bool open;
        bool collapsed;
        position pos;
        int width;
        int height;
    };

    window color_palette;
    window text_editor;
    window browser;

    // Version three: unnamed struct(s) used to simply group certain variables together
    struct
    {
        bool some_setting;
        bool some_other_setting;
        int important_number;
    } general_settings;
};

Version one is simple, and I'm sure it scores better on performance and memory usage than version two and three (if there is any difference, that is). The names can get really long, though, and I'm not a fan of long identifiers.

In version two I can use much shorter identifiers, and I'd really rather identify a property using profile.color_palette.pos.x than profile.color_palette_pos_x. There is one drawback, though. When I type profile. it suggests window which is not a property but a struct and I have no use for that when I'm not working directly inside of profile.h.

Version three solves the problems that I have with versions one and two but it introduces a new problem. When I type profile. it suggests some_setting which I shouldn't be able to access through profile, only through profile.general_settings. Is that just Code::Blocks being weird or is there something fundamental that I just don't know about?

So my questions are (rephrased):

  • Would it be an objectively wrong choice to use any of my sample structures? That is, are any of them marginally worse than the others performance-wise? Do the nested structs use an excessive amount of memory compared to version one? Are any of the versions more prone to bugs than the others? I am simply looking for yes or no answers here so that I know if it would be unwise of me to go with any one of them. Is there, perhaps, something else I should keep in mind when deciding on a structure to use?
  • Are there any unwritten rules or some common knowledge that people have in mind when deciding on whether or not to use nested structs? Again, I'm simply looking for a yes or a no here.
  • Are there any naming conventions that people expect me to use for nested structs (like classes are named LikeThis and getters and setters are usually named like_this)? Should I declare them inside or outside of the scope in which I intend to use them? I have read that people usually advise against a broader scope than necessary and I am simply asking to make sure because I have never before worked in a milieu where classes inside of classes is advisable or even legal.

Solution

    • In this scenario, is there a structure that is objectively preferrable over others? (it can be something I haven't even thought of)

    No, that totally depends on your actual use cases, and what would fit better to solve them

    • Nested structs (at least, nested struct declarations) seem to introduce more problems than they solve, and they drastically reduce readability, so my gut tells me I should avoid it. What is the general consensus on this?

    Nested structs have their use cases per se, most of the time if the nested struct/class is tightly coupled to the declaring outer class/struct (i.e. like standard container iterator classes are coupled to their associated container implementations). Using them without having such tightly coupling relation, it will complicate things, yes.

    • If I do use nested structs, should I use the naming conventions for structs or for variables? Should I declare them inside or outside of the scope in which I intend to use them?

    AFAIK there are no special naming conventions, other than you want to have for compatibility with C++ standard classes' template arguments. If you have such like a nested iterator class definition, name it so and fulfill contracts as demanded from the standard algorithm and other operations.

    As it comes figured out in your sample #2, it seems interfaces (that are declared outside your class) might fit better to assemble such relations and abstractions you need

    struct IPosition {
        virtual int x() const = 0;
        virtual int y() const = 0;
    
        virtual ~IPosition() {}
    };
    
    struct IWindow {
        virtual bool open() const = 0;
        virtual bool collapsed() const = 0;
        virtual const IPosition& pos() const = 0;
        virtual int width() const = 0;
        virtual int height() const = 0;
    
        virtual ~IWindow() {}
    };
    
    struct Profile {
        IWindow& windowOne;
        IWindow& windowTwo;
    
        // Provide an appropriate IWindow implementation instances 
        // when constructing here
        Profile(IWindow& windowOne_, IWindow& windowTwo_) 
        : windowOne(windowOne_)
        , windowTwo(windowTwo_)
        {}
    };