常量和不带相同的功能 - 何时以及为何?

时间:2015-01-07 17:46:28

标签: c++ const

T& f() { // some code ... }
const T& f() const { // some code ... }

我现在已经看过几次了(在我迄今为止一直在研究的介绍性书中)。我知道第一个const使返回值为const,换句话说:不可修改。第二个const允许也可以为const声明的变量调用该函数,我相信。

但为什么你会在同一个类定义中同时拥有这两个函数?编译器如何区分这些?我相信也可以为非const变量调用第二个f()(带const)。

7 个答案:

答案 0 :(得分:25)

  

但为什么你们在同一个类定义中同时拥有这两个函数呢?

两者都允许你:

  • 在可变对象上调用该函数,并根据需要修改结果;和
  • const对象上调用该函数,只查看结果。

只有第一个,你无法在const对象上调用它。只有第二个,你不能用它来修改它返回引用的对象。

  

编译器如何区分这些?

当在const对象上调用函数时(或通过引用或指向const的指针),它会选择const重载。否则它会选择另一个过载。

  

我相信也可以为非const变量调用第二个f()(带const)。

如果这是唯一的超载,那么它可以。对于这两个重载,将选择非const重载。

答案 1 :(得分:12)

但为什么你们在同一个类定义中同时拥有这两个函数?

有时您希望为同一操作提供不同的语义,具体取决于它是否在const对象OR non-const对象上调用。我们举一个std::string类的例子: -

char& operator[](int index);
const char& operator[](int index) const;

在这种情况下,当operator[]通过const对象调用时,您不会让用户更改字符串的内容。

const std::string str("Hello");
str[1] = 'A';     // You don't want this for const.

另一方面,如果是非const字符串,则允许用户更改字符串的内容。这就是为什么不同的过载。

编译器如何区分这些?

编译器检查是否在const对象OR non-const对象上调用该方法,然后适当地调用该方法。

const std::string str("Hello");
cout << str[1];           // Invokes `const` version.

std::string str("Hello");
cout << str[1];           // Invokes non-const version.

答案 2 :(得分:5)

没有const的第一个允许调用者修改对象,该对象通常是调用其方法的类的成员。

第二个,我们的宿主类处于只读模式,也允许对其成员进行只读访问。

默认情况下,如果在constness规则下允许使用非const版本,则会调用非const版本。

其中一个最常见的例子是某种集合/数组类型。

class Array
{
   private:
      MyType members[MySize];

   public:
      MyType & operator[]( size_t index );
      const MyType & operator[]( size_t index ) const;
};

假设它们已经实现,它可能是模板,或者它们是具体的类型和大小。我正在展示const过载。

现在我们可以让某人使用该课程。您可能想要设置一个值。

Array myArray;
myArray[ 3 ] = myObject;

或者你可能只是阅读它:

const Array& myArrayRef = getArrayRef(); // gets it to read
const MyType & myValueRef = myArrayRef[ 3 ];

所以你看我可以使用符号来设置值并读取一个值。 与operator[]一样,您可以将此技术应用于任何方法。

答案 3 :(得分:4)

函数调用parens之后的限定符适用于成员函数的隐藏this参数:

成员函数void Foo::bar()有点像这样:void bar(Foo *this)。但是如果Foo对象是const会发生什么?

struct Foo {
    void bar();
};

const Foo f{};
f.bar();

好吧,由于Foo::bar()采用Foo *this参数(不允许为const),因此上述f.bar();无法编译。因此,我们需要一种方法来限定隐藏的this参数,而C ++选择这样做的方式是允许这些限定符超出函数parens。

编译器区分这些函数的方式在各方面都与常规函数重载完全相同,因为尽管语法很奇怪,但这正是它的本质。

此外,const不是唯一的限定符。您还可以添加volatile限定符,在C ++ 11中,您还可以添加左值和右值参考限定符。


我们需要这个函数的两个几乎完全相同的副本的原因是因为没有直接的方法来解决单一的差异:不同的返回类型。如果我们有一个const对象,并且该对象有一个getter,它返回对它包含的东西的引用,那么该引用需要与整个对象相同。

struct Foo {
  int i;
  int &get_i() const { return i; }
};

int main() {
  const Foo f{};
  f.get_i() = 10; // i should be const!
}

以上不会编译,因为在Foo::get_i() const内,i是const,我们不能返回非const引用。但如果它被允许,那就错了,因为我们不能修改const对象的成员。因此Foo::get_i() const需要将const引用返回给i

int const &Foo::get_i() const { return i; }

但我们应该能够修改非const对象的成员,

int main() {
  Foo f{};
  f.get_i() = 10; // should be fine
}

所以我们不能只有这个功能。当Foo对象本身不是const时,我们需要一个返回非const引用的函数。所以我们根据对象的常量重载函数:

struct Foo {
  int i;
  int const &get_i() const { return i; }
  int &get_i() { return i; }
};

如果功能体更复杂,可以选择避免重复:

struct Foo {
  int i;
  int const &get_i() const { return i; }

  int &get_i() { return const_cast<int &>(const_cast<Foo const *>(this)->get_i()); }
};

也就是说,非const重载将其实现委托给const重载,使用const_cast来修复类型。添加const总是安全的。使用const_cast删除const只有在我们确定原始对象不是const时才是安全的。我们知道在这种情况下,因为我们知道我们首先将const添加到非const对象中。

答案 4 :(得分:4)

Qt框架中有一个很好的例子。

查看QImage课程。

有两个公共职能:

const uchar* scanLine (int i) const;
uchar* scanLine (int i);

第一个仅用于读访问。第二个是针对您想要修改扫描线的情况。

为什么这种区别很重要? 因为Qt使用implicit data sharing。这意味着,如果您执行以下操作,QImage不会立即执行深层复制:

QImage i1, i2;
i1.load("image.bmp");
i2 = i1;                        // i1 and i2 share data

相反,仅在您调用实际修改两个图像之一的函数(如非const scanLine)时才复制数据。

答案 5 :(得分:3)

它允许您以只读方式访问const实例数据,同时仍然能够修改非const实例数据。

#include <iostream>

class test
{
  public:
    test() : data_(0) {}

    int& f() { return data_; }
    const int& f() const { return data_ }

  private:
    int data_;
};

int main(void)
{
  const test rock;
  test paper;

  /* we can print both */
  std::cout << rock.f() << std::endl;
  std::cout << paper.f() << std::endl;

  /* but we can modify only the non const one */
  // rock.f() = 21;
  paper.f() = 42;

}

答案 6 :(得分:3)

如前所述,您可以使用const和非const版本的函数,具体取决于调用对象的常量。范例经常与operator[]一起用于数组。避免代码重复的一种方法(取自Scott Meyers&#39;书籍Effective C ++)是const_cast const函数返回非常量重载,例如:

// returns the position of some internal char array in a class Foo
const char& Foo::operator[](std::size_t position) const
{
    return arr[position]; // getter 
}

// we now define the non-const in terms of the const version
char& Foo::operator[](std::size_t position) 
{
    return const_cast<char&>( // cast back to non-const
        static_cast<const Foo&>(*this)[position] // calls const overload
    ); // getter/setter
}