与非变异集合的常量正确性混淆

Const-correctness confusion with non-mutating collections

假设我有一个 Widget class 和一个 Widget 的集合:

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

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

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

WidgetCollection::findNiceWidget() 允许访问集合中满足某些条件的 Widget 之一。

现在,我有一个 class WidgetTags,它允许将标签分配给 Widget。标签不是 Widget 的一部分,但存储在 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 { /*...*/ }
};

class 只处理指向 const Widget 的指针,因为它从不更改任何 Widget,它只是将其他数据与它们相关联。

然后我介绍三个函数来处理这些结构。

// 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();
}

我现在面临的问题是 mutateNiceWidgets() 显然不能调用 mutate() 因为它只能常量访问 WidgetTags 返回的 Widgets,即使我通过 [=19] =] 作为非常量引用。 为了解决这个问题,我将不得不重写 WidgetTags 以存储指向非常量 Widget 的指针并为它们提供非常量 getWidgetsWithTag()。但是如果我这样做,那么 WidgetTags::addTag() 也必须接受指向非常量的指针。 如果我这样做 that,那么我会在 tagWidgets() 中遇到问题,然后必须接受对 WidgetCollection 的非常量引用,在我看来这是不正确,因为它不应该改变集合(事实上,调用代码甚至可能无法提供非常量引用)。

我对在这种情况下应该如何应用 const 正确性感到困惑。

在我的用例中,所有 Widget 都由 WidgetCollection 管理和拥有。所以从理论上讲,如果您拥有对整个 WidgetCollection 的非常量访问权限,那么您应该被授予对 Widget 的非常量访问权限,无论您如何获得 Widget。 这让我想到实施这样的事情:

class WidgetCollection
{
    // ...

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

并重写mutateNiceWidgets()

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

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

但这感觉非常不对。

任何关于如何正确选择什么应该是 const 什么不应该是 const 的想法都值得赞赏。

您使用指针有两个原因;身份和访问控制。

从这里开始:

struct WidgetId;

它现在是抽象的(不是虚拟抽象;但我们没有指定其中的内容。但是请将其设为常规值类型)。 WidgetId 唯一命名一个小部件。

现在WidgetTags是身份和标签之间的多重双向映射。

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

你的问题就消失了。

现在,WidgetId 可以只是一个整数,或者 Widget const*,或者内部的任何东西。

但是通过将访问权限与身份分开,您的问题就迎刃而解了。如果小部件存储不那么简单,最终用户也不会注意到。