为什么常量方法不能返回非常量引用?

4
下面的方法getRanks()为什么不能编译?如何优雅地修复它?
我只是想定义一个成员访问器方法,返回对成员的引用。该引用不是const的,因为我可能稍后修改它所引用的内容。但由于成员函数不会修改对象,所以我将其声明为const。然而,编译器(clang,std=c++11)坚持认为存在“引用绑定”会“丢失限定符”的问题。但我没有丢失限定符,是吗?如果是,为什么呢?
struct teststruct{
  vector<int> ranks;
  vector<int>& getRanks()const{
    return ranks;
  }
};

现在,如果我更改返回语句以去除const,则代码将编译:
return const_cast<vector<int>&>(ranks);

但是,“等级”一词本来就不应该是常量,我不明白为什么需要消除常量的const_cast。我甚至不确定这样做是否安全。

无论如何,有没有更简洁的方法来编写此方法?有人能解释为什么这样一个简单的常识方法会失败吗?我确实希望将getRanks()方法声明为“const”,以便我可以从其他const方法中调用它。


teststruct const x; x.getRanks().emplace_back(); -> 未定义行为。 - dyp
4个回答

7

const成员函数背后的思想是你应该能够在const对象上调用它们。 const函数无法修改对象。

假设你有一个类:

class A
{
   int data;
   void foo() const
   {
   }
};

关于对象和函数调用:

A const a;
a.foo();

A::foo 函数内,this->data 被视为 int const 类型,而不是 int 类型。因此,在 A:foo() 中无法修改 this->data
对于您的示例,getRanks() 函数中的 this->ranks 类型应视为 const vector<int> 而不是 vector<int>。由于不允许自动转换为 vector<int>&,因此当您将函数定义为以下内容时,编译器会报错:
vector<int>& getRanks()const{
    return ranks;
  }

它不会报错,如果你将函数定义为以下格式:
const vector<int>& getRanks()const{
    return ranks;
  }

由于const vector<int>可以自动转换为const vector<int>&


4

ranksconst的,因为封闭对象(*this)是const的,所以你必须返回一个std::vector<int> const的引用。

如果你想允许客户端修改向量(从而影响成员变量),那么getter就不应该是const的。请注意,getter本身就很愚蠢,因为ranks已经是一个公共数据成员了。


如果getter不是const,它就不能从const方法中调用。但我想这样做,有时我想从const方法中调用它,有时从非const方法中调用。 - kdog
4
请提供上下文,以便我更好地理解需要翻译的内容。 - user1804599
1
关于 std::vector::operator[] 的内容,请参考链接:http://en.cppreference.com/w/cpp/container/vector/operator_at。 - Joseph Mansfield

1
你正在返回对teststruct成员ranks的引用。这意味着任何获得此引用的人都可以修改teststruct对象的内部。因此,const是虚假的。
不要强制转换const。相反,决定函数是否应为const并返回ranks的副本或const引用,还是非const并返回可变引用。如果必要,两者都可以有。

但是等一下。我以为在方法声明的分号之前只有一个“const”,这意味着该方法本身不会修改其作为成员函数的对象。如果我没记错的话,Stroustrup的书上就是这么说的。我并没有声明从现在到未来任何使用返回值的方式都不会修改对象。我只是声明该函数本身不会修改对象。而且我确实想返回一个可以修改的引用,而不是副本!另外,const_cast的想法安全吗? - kdog
1
@kdog 好吧,这将允许该方法间接地导致对象被修改。const_cast 的想法并不安全,因为编译器可以根据它认为它不会被修改的事实做出假设。 - Joseph Mansfield
与其说“const是一个谎言”,我更倾向于说“const/返回类型破坏了常量正确性”。 - dyp
不,编译器应该在v.push_back(42)行报错,而不是在getRanks()行...尽管我开始看到问题所在。为什么这对我来说如此混乱和反直觉,而对其他人来说却显而易见呢;-) - kdog
1
@kdog 对于成员函数的 const 意味着该函数被调用的对象是 const 的。换句话说,this 指针是 const 的。因为 this 是 const,所以 this->anything 也是 const 的。虽然 getRanks 不需要返回一个 const &,但是必须返回一个与返回类型相同的对象。 - nwp
@kdog 编译器无法强制执行“不修改某些内容”。在具有指针算术和 const_cast 以实现兼容性的低级语言中,这是不可能的。它必须依赖于您的注释。因此,即使一个不修改对象的函数,如果没有声明为 const,则不能在 const 对象上调用。在这里,情况类似。“const 成员函数不修改其对象”仅仅是一种约定,需要考虑 mutable 对象等情况。 - dyp

0

它会丢弃限定符,因为您返回对 ranks 的引用。之后的任何代码都可以修改 ranks。例如:

auto v = teststruct.getRanks();
v[1] = 5; //assuming v.size() > 1

你可以通过返回一个副本来解决这个问题:

vector<int> getRanks() const

或者一个 const 引用:

const vector<int>& getRanks() const

如果您想在一个const对象中使ranks可变,您可以这样做:
mutable vector<int> ranks;

不,这并不能解决问题,因为我可能希望稍后修改getRanks()返回的引用。我需要两个getRanks,一个是const的,一个不是?我只想声明getRanks()本身不会修改对象。它可以在const方法中调用。 - kdog
不,我不希望等级可变,我希望进行常量检查。 - kdog
那么看起来你需要两个重载函数 vector<int>& getRanks();const vector<int>& getRanks() const; - aschepler

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