在C++中,为什么编译器会选择非const函数,而const函数也能正常工作呢?

21
例如,假设我有一个类:
class Foo
{
public:
    std::string& Name()
    {
        m_maybe_modified = true;
        return m_name;
    }

    const std::string& Name() const
    {
        return m_name;
    }
protected:
    std::string m_name;
    bool m_maybe_modified;
};

在代码的其他地方,我有类似这样的东西:
Foo *a;
// Do stuff...
std::string name = a->Name(); // <-- chooses the non-const version

有人知道为什么编译器在这种情况下会选择非const版本吗?
这只是一个有点牵强的例子,但我们真正要解决的问题是定期自动保存一个对象,如果它发生了变化,指针必须是非const的,因为它可能在某个时刻被改变。

如果您想在const函数中更改某些变量,请使用mutable属性。 - ypnos
...还有“成员变量”,我想。 - Niklas
4个回答

20

脑海中浮现出两个答案:

  1. 非const版本更匹配。

  2. 如果它在非const情况下调用了const重载,那么在什么情况下它会调用非const重载呢?

您可以通过将a强制转换为const Foo *来使用另一个重载。

编辑:来自C++ Annotations

早些时候,在第2.5.11节中介绍了函数重载的概念。在那里指出,成员函数可以仅通过其const属性进行重载。在这些情况下,编译器将使用最接近对象const限定符的成员函数:


18

因为a不是一个const指针,所以非const函数更匹配。以下是如何调用const函数的方法:

const Foo* b = a;
std::string name = b->Name();

如果你有一个常量和一个非常量重载,并且想在非常量对象上调用常量重载,这可能是设计不良的一个迹象。


完整的设计很难用一个例子来表达,但有一些可以改进的地方,我相信...目前我们使用const指针方法,但这很丑陋,更重要的是依赖于程序员记得这样做。 - Caleb Huitt - cjhuitt
1
给const重载一个不同的名称怎么样?或者有一个const重载和一个命名不同的函数,其中一个在内联中调用另一个呢? - Lev
只是挑刺一下...指针不是const...在你的例子中它是“指向const的指针”。 - Pat Notz

7
编译器在确定返回值时不考虑其使用方式;这不是规则的一部分。它不知道你是否正在进行。
std::string name = b->Name();

或者

b->Name() = "me";

必须选择适用于两种情况的版本。


问题不在于返回值是否为const,而在于对象的const性质。但是-1并不是我。 - Mike F
我理解编译器可能会出现的问题,但我认为它应该能够进行一些简单的检查,比如在这个示例中,它会立即使用返回的副本然后将其丢弃。 - Caleb Huitt - cjhuitt
那将是相当危险的,因为对其他代码行的更改也会导致特定调用的行为发生变化。决定重载解析的规则已经足够复杂了。 - Michael Burr

3
你可以添加一个"cName"函数,该函数相当于"Name() const"。这样你就可以在不先将对象强制转换为const的情况下调用函数的const版本。
这在C++0x中使用新关键字auto时非常有用,这也是他们正在更新库以包括cbegin()、cend()、crbegin()和crend()函数来返回const_iterator,即使对象是非const的。
你所做的事情可能最好是通过有一个setName()函数来改变名称,而不是返回对底层容器的引用,然后“可能”它被修改了。

我意识到使用setName()函数可能更好,但是这个例子有些牵强。实际上,我们有指向包含其他对象的对象的指针,并且在像“int c = a->B()->GetC();”这样串联起来时,希望尽可能返回const指针。 - Caleb Huitt - cjhuitt

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