在成员函数声明的末尾,'const' 的含义是什么?

870
在这些声明中,const 的意思是什么?
class foobar
{
  public:
     operator int () const;
     const char* foo() const;
};
12个回答

1139

当你在一个方法中添加const关键字时,this指针本质上将成为指向const对象的指针,因此你不能更改任何成员数据。(除非你使用mutable,稍后会讲到)。

const关键字是函数签名的一部分,这意味着你可以实现两个类似的方法,一个在对象为const时调用,另一个不是。

#include <iostream>

class MyClass
{
private:
    int counter;
public:
    void Foo()
    { 
        std::cout << "Foo" << std::endl;    
    }

    void Foo() const
    {
        std::cout << "Foo const" << std::endl;
    }

};

int main()
{
    MyClass cc;
    const MyClass& ccc = cc;
    cc.Foo();
    ccc.Foo();
}

这将会输出

Foo
Foo const

在非const方法中,您可以更改实例成员,而在const版本中则无法这样做。如果您将上面示例中的方法声明更改为以下代码,则会出现一些错误。

    void Foo()
    {
        counter++; //this works
        std::cout << "Foo" << std::endl;    
    }

    void Foo() const
    {
        counter++; //this will not compile
        std::cout << "Foo const" << std::endl;
    }

这并不完全正确,因为您可以将成员标记为mutable,然后const方法可以更改它。它主要用于内部计数器等内容。解决方案是以下代码。

#include <iostream>

class MyClass
{
private:
    mutable int counter;
public:

    MyClass() : counter(0) {}

    void Foo()
    {
        counter++;
        std::cout << "Foo" << std::endl;    
    }

    void Foo() const
    {
        counter++;    // This works because counter is `mutable`
        std::cout << "Foo const" << std::endl;
    }

    int GetInvocations() const
    {
        return counter;
    }
};

int main(void)
{
    MyClass cc;
    const MyClass& ccc = cc;
    cc.Foo();
    ccc.Foo();
    std::cout << "Foo has been invoked " << ccc.GetInvocations() << " times" << std::endl;
}

输出结果如下:

Foo
Foo const
Foo has been invoked 2 times

6
如果我只创建一个常量方法但不创建普通方法,然后使用非常量对象调用这个方法,我的代码通常都能正常运行。这样做是否有问题或者有害之类的? - KhiemGOM
3
@KhiemGOM 这完全没问题,对于只读成员来说这是很正常的模式。 - Mats Fredriksson
mutableconst 对我来说是有意义的。但我不太理解的是,如果一个 const 成员函数想要改变一个成员变量的值,为什么不丢弃 const 并将自己变为非 const 的成员函数呢?这与速度有关吗?还是说在一个 const 成员函数中,给定两个成员变量,一个带有 mutable 关键字,另一个没有,我们只想更改具有 mutable 关键字的变量的值,但希望保持另一个变量不变? - Lion Lai
1
const函数可以帮助提高速度。如果编译器看不到函数的实现(它在cpp文件中定义,或者调用了在cpp文件中定义的函数),那么它就不知道它可能改变哪些成员变量。如果在函数调用之前,任何一个成员变量都保存在寄存器中,并且在函数调用后使用,编译器必须假设这些值可能已经被改变,并从内存中重新加载它们。如果成员变量是不可变的,那么编译器可以推断出函数没有改变它们,并且可以在调用后跳过从内存中加载它们的步骤。 - Uri Raz

211

51

const限定符表示可以在任何foobar值上调用方法。当您考虑在常量对象上调用非常量方法时,差异就出现了。假设您的foobar类型具有以下额外方法声明:

class foobar {
  ...
  const char* bar();
}

bar()方法是非const的,只能从非const值中访问。

void func1(const foobar& fb1, foobar& fb2) {
  const char* v1 = fb1.bar();  // won't compile
  const char* v2 = fb2.bar();  // works
}
< p > "const" 的理念是标记那些不会改变类的内部状态的方法。这是一个强大的概念,但在 C++ 中实际上是无法执行的。它更像是一个承诺而不是保证。这个承诺经常被打破,也很容易被打破。

foobar& fbNonConst = const_cast<foobar&>(fb1);

3
我认为答案涉及到其他const方法而不是const对象。 - Mykola Golubyev
谢谢你的解释:“const 的背后的想法是标记不会改变类内部状态的方法。”这正是我在寻找的。 - kovac
1
@JaredPar 这是否意味着任何代表只读操作的成员函数都应该标记为 const - kovac

31

这些const关键字意味着,如果带有'with const'方法更改了内部数据,编译器将会报错。

class A
{
public:
    A():member_()
    {
    }

    int hashGetter() const
    {
        state_ = 1;
        return member_;
    }
    int goodGetter() const
    {
        return member_;
    }
    int getter() const
    {
        //member_ = 2; // error
        return member_;
    }
    int badGetter()
    {
        return member_;
    }
private:
    mutable int state_;
    int member_;
};

测试

int main()
{
    const A a1;
    a1.badGetter(); // doesn't work
    a1.goodGetter(); // works
    a1.hashGetter(); // works

    A a2;
    a2.badGetter(); // works
    a2.goodGetter(); // works
    a2.hashGetter(); // works
}

点击这里了解更多信息。


该链接提供有关C++中常量正确性的更多信息。

2
如果一个关于const成员函数的问题没有提到mutable,那么它最多只能算是不完整的。 - IInspectable

14

Blair的回答很到位。

但请注意,可以在类的数据成员中添加mutable限定符。任何标记为 mutable 的成员都可以在const方法中进行修改而不违反const协定。

如果您想让对象记住调用特定方法的次数,同时不影响该方法的“逻辑”不可变性,则可能希望使用此选项(例如)。


13
C++ Common Knowledge: Essential Intermediate Programming中,“Const Member Function的含义”一文给出了明确的解释:
在类X的非const成员函数中,this指针的类型是X * const。也就是说,它是一个指向非常量X的常量指针(参见“Const Pointers and Pointers to Const [7, 21]”)。因为this所指的对象不是const,所以它可以被修改。在类X的const成员函数中,this指针的类型是const X * const。也就是说,它是一个指向常量X的常量指针。因为this所指的对象是const,所以它不能被修改。这就是const和non-const成员函数之间的区别。
因此,在您的代码中:
class foobar
{
  public:
     operator int () const;
     const char* foo() const;
};

你可以这样想:
class foobar
{
  public:
     operator int (const foobar * const this) const;
     const char* foo(const foobar * const this) const;
};

this 不是 const。它不能被修改的原因是它是一个 prvalue。 - Brian Bi

11

我想添加以下观点。

你还可以将其声明为 const &const &&

因此,

struct s{
    void val1() const {
     // *this is const here. Hence this function cannot modify any member of *this
    }
    void val2() const & {
    // *this is const& here
    }
    void val3() const && {
    // The object calling this function should be const rvalue only.
    }
    void val4() && {
    // The object calling this function should be rvalue reference only.
    }

};

int main(){
  s a;
  a.val1(); //okay
  a.val2(); //okay
  // a.val3() not okay, a is not rvalue will be okay if called like
  std::move(a).val3(); // okay, move makes it a rvalue
}

欢迎对答案进行改进。我不是专家。


1
即使成员函数是右值引用限定符并在右值上调用,*this 也始终是左值。示例 - HolyBlackCat
更新了。可以吗? - coder3101

7

当你在方法签名中使用const(就像你所说的:const char* foo() const;),你告诉编译器,被this指向的内存不能被此方法(这里是foo)更改。


5

这里的 const 表示在该函数中,任何变量的值都不能被改变。

class Test{
private:
    int a;
public:
    void test()const{
        a = 10;
    }
};

就像这个例子一样,如果你尝试在测试函数中更改变量的值,你将会收到一个错误。


这个回答对最高评分的回答没有任何补充。 - ExaltedBagel

2

https://isocpp.org/wiki/faq/const-correctness#const-member-fns

What is a "const member function"?

A member function that inspects (rather than mutates) its object.

A const member function is indicated by a const suffix just after the member function’s parameter list. Member functions with a const suffix are called “const member functions” or “inspectors.” Member functions without a const suffix are called “non-const member functions” or “mutators.”

class Fred {
public:
  void inspect() const;   // This member promises NOT to change *this
  void mutate();          // This member function might change *this
};
void userCode(Fred& changeable, const Fred& unchangeable)
{
  changeable.inspect();   // Okay: doesn't change a changeable object
  changeable.mutate();    // Okay: changes a changeable object
  unchangeable.inspect(); // Okay: doesn't change an unchangeable object
  unchangeable.mutate();  // ERROR: attempt to change unchangeable object
}

The attempt to call unchangeable.mutate() is an error caught at compile time. There is no runtime space or speed penalty for const, and you don’t need to write test-cases to check it at runtime.

The trailing const on inspect() member function should be used to mean the method won’t change the object’s abstract (client-visible) state. That is slightly different from saying the method won’t change the “raw bits” of the object’s struct. C++ compilers aren’t allowed to take the “bitwise” interpretation unless they can solve the aliasing problem, which normally can’t be solved (i.e., a non-const alias could exist which could modify the state of the object). Another (important) insight from this aliasing issue: pointing at an object with a pointer-to-const doesn’t guarantee that the object won’t change; it merely promises that the object won’t change via that pointer.


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