与非变异集合的常量正确性混淆
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*
,或者内部的任何东西。
但是通过将访问权限与身份分开,您的问题就迎刃而解了。如果小部件存储不那么简单,最终用户也不会注意到。
假设我有一个 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*
,或者内部的任何东西。
但是通过将访问权限与身份分开,您的问题就迎刃而解了。如果小部件存储不那么简单,最终用户也不会注意到。