const输入 vs 非const输出

15

我有一个函数,遍历一个对象树,并且不会修改任何在该树中的对象。

该函数大致如下:

static Node* findMatchingNode(const Node& root, const SomeFilterData& d);

struct Node {
   Node* left;
   Node* right;
};

该函数可以返回树中的根节点或任何对象,也可以不返回任何内容。显然,根据给定声明,我必须在某个地方执行const_cast,但在大多数情况下这是被禁止的。

一个函数能否保证其不变性,并同时允许任何人修改其输出?

编辑。我没有清楚地说明,我的函数确实不会修改树中的任何节点,也不会创建新节点,它是一个纯函数。我希望始终在那里使用const限定符,以清楚地告诉所有人该函数不会修改任何东西

编辑。背后未明确说明的问题是,在函数执行期间(仅在函数内部),没有合法的方法来表达输入的不变性,而不强制执行输出的不变性。


1
为什么很明显“在给定的声明中,我必须在某个地方执行const_cast”? - dlavila
@dlavila:因为他返回了一个非const的结果。 - Mooing Duck
我以前遇到过这个问题,但害怕寻求帮助。+1 - Carlton
C++标准库存在一个相关问题:通过迭代器指定某个位置修改数据结构。在这种情况下,由于修改需要对数据结构进行非const访问,因此const迭代器参数就足够了。例如:vector::insert。但我不确定是否可以应用这样的分离,因为缺少上下文信息。 - dyp
@dlavila:因为该函数可能通过一个返回指针返回const类型的根节点,而该指针本身是non-const类型。但是,使用const_cast可能不需要返回链接节点。 - Brent Bradburn
5个回答

7
一如既往,您无法修改 const 数据,因为它是常量。特别地:
尽管 const_cast 可以从任何指针或引用中移除 constness 或 volatile,但使用结果指针或引用写入已声明为 const 的对象或访问已声明为 volatile 的对象将调用未定义的行为。
(来自 这里)
因此,如果指向 const 输入参数 root 的指针是合理的结果,则返回类型也应该是 const。无论如何,您在返回指针时都应该非常小心:目前,您的函数可以返回临时指针!
所以最好返回一个 boost::optional<Node>std::optional<Node>(如果后者进入 C++17 并且您使用该标准)。
如果您的函数仅适用于可修改的输入root(例如,如果结果必须是可修改的结果可以是root的地址),则在声明中去掉const。这也将防止输入root成为临时变量。

最可能解决您潜在的XY问题的最佳方法:

如果它适合您的用例(我发现这很不可能不适合),则更好的选项是定义您的算法在可迭代数据结构上,如树或列表(无论您的Node是节点),然后返回匹配节点的迭代器,或者是过去的结束迭代器(相对于高级结构)。

至于const与非const讨论:通过使用迭代器选项,您可以通过对高级结构使用非const迭代器和对比较引用节点使用const&参数来区分高级结构的常量性和非常量性(其中临时变量现在不是问题)。

从您的问题中可以看出,您的函数甚至可以被std::find_if替换。那么您就不会有这个问题了。


如果const是逻辑常量,则去除const是安全的。 - billz
“因此,临时变量没有指针。”你说这个函数可能会返回一个指向root的指针。那么如果我像这样调用它auto p = findMatchingNode(Node{}, d);会发生什么?它可能会返回临时参数的地址,即使你返回const也是不好的!在返回指针和引用时要非常小心。即使C++标准委员会也几乎被烧毁了! - Baum mit Augen
@ Baum mit Augen,我理解其中的危险。希望在我的情况(真实情况)中,节点无法在范围内分配。感谢您的回答。 - user24601
首先,相信用户不会愚蠢至少是大胆的。现在我感觉实际问题在于你手工制作东西,而不是考虑你实际想要做的抽象算法。如果我们有一个std::find_if(tree.begin(). tree.end(), isMatching(someNode, d));而不是一些函数findMatchingNode,我们就不需要在第一时间进行这个讨论了。 - Baum mit Augen
@Baum mit Augen,第一篇帖子中的代码仅为示例,因此std::find_if永远不适用。没有任何FilterData。相反,有一个int | string | whatever键的列表。没有节点。相反,有std :: vector和std :: map。 - user24601
显示剩余4条评论

6

您的代码不够const-correct,它删除了一个const。 这就是为什么您会遇到“必需”的const转换问题的原因。请不要这样做:

static const Node* findMatchingNode(const Node& root, const SomeFilterData& d);

您可能会指出,您可能希望从另一个修改节点的函数中调用此函数,因此需要一个非const结果。因此,应该创建一个具有完全不同签名的新函数:

static Node* findMatchingNode(Node& root, const SomeFilterData& d);

你可能指出这些对象具有相同的结构,而且还有DRY原则(DRY =不要重复自己=不要复制粘贴代码)。是的,在这里可以采取一种捷径:const_cast。我认为在这种情况下使用它是可以的,因为它的唯一目的是共享代码,并且清楚地表明它没有违反任何const-correctness原则。
//This function does not modify anything
static Node* findMatchingNode(Node& root, const SomeFilterData& d) {
    return const_cast<Node*>(findMatchingNode(const_cast<const Node&>(root),d));
}

Loki Asari 建议添加第三个私有函数 findMatchingNodeCommon(),这个函数可以被两个版本的findMatchingNode()调用。这样你就可以省去一个 const_cast 了。如果你想走极端,那么你可以将 findMatchingNodeCommon() 设为模板,这样所有的 const_cast 都会消失。我认为这并不值得麻烦,但是这些意见非常有价值,所以值得一提。


这是我见过的第一个非滥用 const_cast 的例子。 - Carlton
2
个人而言,我会添加第三个私有函数 findMatchingNodeCommon(),两个版本的 findMatchingNode() 都调用它。这样你就可以少用一个 const_cast。如果你想走极端,那么你可以将 findMatchingNodeCommon() 设为模板,然后所有的 const_cast 都消失了。 - Martin York
第二个函数不符合OP的要求 - 它没有明确保证不修改树中的对象。(我对这是否是一个“明智”的要求没有意见,但如果你认为它不是,我认为你应该明确地表达出来。) - Harry Johnston
1
谢谢,我知道这个技巧。findMatchingNode 可以转换成模板函数,这样它就会自动推断常量性。我只是对于确保输入常量性的函数必须真正告诉用户是否允许修改其输出这一事实感到困惑。 - user24601

3
根据Standard C++ Foundation返回引用和const成员函数之间有什么关系? 如果您想从检查器方法中引用返回此对象的成员,应使用const引用(const X& inspect() const)或按值返回(X inspect() const)。
因此,您应该通过值或const引用/指针返回。虽然您没有使用this,但模式是相同的。

诀窍在于在实现时牢记DRY原则。 - Mooing Duck
@MooingDuck,然而这并不是问题的关键,因此这个答案没有提到任何DRYng :) 而且,由于他对const正确性的认识,我认为他可能知道如何避免重复自己。 - Denilson Amorim

0
我会看看STL是如何做的。在那里,你有一个由迭代器分隔的序列,一个定义匹配的谓词和一个搜索的模板函数。由于它是一个模板函数,它自动将返回值的const限定符与参数匹配。
如果出于某种原因,您不想编写模板函数,您还可以添加两个重载,因为您可以使用不同的const限定符重载函数。

我知道这个技巧。我只是想始终在那里使用“const”限定符,以清楚地告诉每个人该函数不会修改任何内容。 - user24601

0
我想回答一下自己,因为原始问题没有被正确表述。所以,我的答案不能被视为正式的答案。
在不强制用户不修改输出(并且不强制输出常量)的情况下,在函数执行期间表达输入的常数是没有合法的方法的。
一种不使用_const_cast_的方法:
static Node* findMatchingNode(Node& root, const SomeFilterData& d);

看起来不保证输入树的常量性。

原始函数声明允许像这样的黑客,这也是不合适的:

const Node& root = ...
Node* result = findMatchingNode(root, filter);
result->doBadNonConstThings();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接