简体   繁体   中英

Const-correctness confusion with non-mutating collections

Assume that I have a Widget class and a collection of Widget s:

class Widget
{
public:
    void mutate() {}
};

class WidgetCollection
{
private:
    std::vector<Widget> vec;

public:
    Widget* findNiceWidget() { /*...*/ }
    const Widget* findNiceWidget() const { /*...*/ }
};

WidgetCollection::findNiceWidget() gives access to one of the Widget s in the collection that fulfills some criteria.

Now, I have a class WidgetTags which allows to assign tags to Widget s. Tags are not part of Widget s, but are stored outside in this class:

class Tag {};

class WidgetTags
{
private:
    std::multimap<const Widget*, Tag> tags;

public:
    void addTag(const Widget* w, Tag t) { /*...*/ }
    std::vector<const Widget*> getWidgetsWithTag(Tag t) const { /*...*/ }
};

The class only deals with pointers to const Widget s as it never changes any Widget , it just associates additional data with them.

Then I introduce three functions to work on these structures.

// Takes Widgets from the WidgetCollection and assigns tags to them in an instance of WidgetTags
void tagWidgets(const WidgetCollection& wc, WidgetTags& wt)
{
    Tag niceTag;
    wt.addTag(wc.findNiceWidget(), niceTag);
}

// Prints all Widgets which have the niceTag tag
void printNiceWidgets(const WidgetTags& wt)
{
    Tag niceTag;
    for (auto w : wt.getWidgetsWithTag(niceTag))
        std::cout << w;
}

// Calls a mutating function on all Widgets which have the niceTag tag
void mutateNiceWidgets(const WidgetTags& wt)
{
    Tag niceTag;
    for (auto w : wt.getWidgetsWithTag(niceTag))
        w->mutate();
}

The problem I am facing now is that mutateNiceWidgets() obviously cannot call mutate() as it only has const access to the Widgets returned by WidgetTags , even if I pass WidgetTags as a non-const reference. To solve this, I would have to rewrite WidgetTags to store pointers to non-const Widget s and provide them with a non-const getWidgetsWithTag() . But if I do that, then WidgetTags::addTag() would have to accept pointers to non-const as well. And if I do that , then I get problems in tagWidgets() , which would then have to accept a non-const reference to WidgetCollection , which in my opinion is incorrect, as it is not supposed to alter the collection (and in fact, the calling code might not even be able to provide a non-const reference).

I am confused as to how const correctness is supposed to be applied in this situation.

In my use case, all Widget s are managed and owned by the WidgetCollection . So theoretically, you should be granted non-const access to a Widget if you have non-const access to the whole WidgetCollection , no matter how you obtained the Widget . Which makes me think about implementing something like this:

class WidgetCollection
{
    // ...

    Widget* makeNonConst(const Widget* w)
    {
        return const_cast<Widget*>(w);
    }
};

And rewriting mutateNiceWidgets() :

void mutateNiceWidgets(WidgetCollection& wc, const WidgetTags& wt)
{
    Tag niceTag;

    for (auto w : wt.getWidgetsWithTag(niceTag))
        wc.makeNonConst(w)->mutate();
}

But this feels extremely wrong.

Any thoughts on how to make the right choice about what should be const and what shouldn't are appreciated.

You are using pointers for two reasons; identity and access control.

Start with this:

struct WidgetId;

it is abstract for now (not virtual abstract; but we don't specify what is in it. Do make it a regular value type however). A WidgetId names a widget uniquely.

Now WidgetTags is a multi-bimap between identities and tags.

WidgetId findNiceWidget() const { /*...*/ }
const Widget* getWidget(WidgetId) const { /*...*/ }
Widget* getWidget(WidgetId) { /*...*/ }
WidgetId getWidgetId(Widget const*) const { /*...*/ }

and your problem goes away.

Now, WidgetId could just be an integer, or a Widget const* , or whatever internally.

But by splitting access from identity, your problem goes away. And if widget storage isn't as simple, the end user doesn't notice.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM