错误:将const xxx作为成员函数的'this'参数传递会丢弃限定符。

521
#include <iostream>
#include <set>

using namespace std;

class StudentT {

public:
    int id;
    string name;
public:
    StudentT(int _id, string _name) : id(_id), name(_name) {
    }
    int getId() {
        return id;
    }
    string getName() {
        return name;
    }
};

inline bool operator< (StudentT s1, StudentT s2) {
    return s1.getId() < s2.getId();
}

int main() {

    set<StudentT> st;
    StudentT s1(0, "Tom");
    StudentT s2(1, "Tim");
    st.insert(s1);
    st.insert(s2);
    set<StudentT> :: iterator itr;
    for (itr = st.begin(); itr != st.end(); itr++) {
        cout << itr->getId() << " " << itr->getName() << endl;
    }
    return 0;
}

在行:

cout << itr->getId() << " " << itr->getName() << endl;

这个代码出现了错误:
../main.cpp:35: 错误:将 'const StudentT' 作为 'this' 参数传递给 'int StudentT::getId()' 会丢弃限定符
../main.cpp:35: 错误:将 'const StudentT' 作为 'this' 参数传递给 'std::string StudentT::getName()' 会丢弃限定符
这段代码有什么问题?

15
你的代码片段中第35行在哪里? - In silico
145
我希望GCC能改进这个错误提示,例如将“discards qualifiers”更改为“breaks const correctness”。 - jfritz42
15
@jfritz42:如果省略 volatile 关键字,会让情况变得混乱。 - PlasmaHH
5
错误信息将分为“破坏常量正确性”和“破坏易失性正确性”。现在,很少有人会考虑某些东西是否易失性正确 - Caleth
4个回答

598
std::set 中存储的对象为 const StudentT。因此,当您尝试使用 const 对象调用 getId() 时,编译器会检测到问题,主要是您正在对常量对象调用非常量成员函数,这是不允许的,因为非常量成员函数不能保证不修改对象;所以编译器会做出一个安全假设,即 getId() 可能会尝试修改对象,但同时也注意到对象是常量;因此,任何尝试修改常量对象的操作都应该是错误的。因此,编译器会生成一个错误消息。
解决方法很简单:将函数改为 const,例如:
int getId() const {
    return id;
}
string getName() const {
    return name;
}

这是必要的,因为现在您可以在常量对象上调用getId()getName(),如下:

void f(const StudentT & s)
{
     cout << s.getId();   //now okay, but error with your versions
     cout << s.getName(); //now okay, but error with your versions
}
作为旁注,你应该将operator<实现为:
inline bool operator< (const StudentT & s1, const StudentT & s2)
{
    return  s1.getId() < s2.getId();
}

注意参数现在是const引用。


3
非常清晰的解释,谢谢。 但是我对你最后的代码片段有疑问。为什么在函数参数中使用引用 const StudentT & s1, const StudentT & s2 - Rafael Adel
3
为避免不必要的复制,您可以使用引用,而因函数不需要修改对象,所以使用const可以在编译时强制实施这一点。 - Nawaz

100

不修改类实例的成员函数应声明为const:

int getId() const {
    return id;
}
string getName() const {
    return name;
}

无论何时看到“discards qualifiers”,它都与constvolatile有关。

2
@Fred - 你认为给不修改类实例的成员函数添加const修饰符绝对是必需的吗?在这种情况下是否还有其他原因导致错误?我怀疑这一点,因为在我编写的大多数getter中,我没有添加const修饰符。 - Mahesh
@Mahesh:我的代码审核不会通过。我有一个同事称呼我为“常量警察”。8v)将foo obj;更改为const foo obj;,看看会发生什么。或者传递一个const引用给foo - Fred Larson
@Fred - 这是完全不同的情况。我知道const对象只能调用const成员函数,而const成员函数只能调用另一个带有const限定符的成员函数。但在这种情况下,我不明白为什么std::set将其存储为const StudentT,正如@Nawaz所提到的那样。 - Mahesh
3
@Mahesh:就像我之前所说的那样——如果更改了set中的元素,顺序可能会被打破,那么set就不再有效。在map中,只有键是常量。在set中,整个对象实际上是键。 - Fred Larson
1
@Mahesh:const 是必要的,否则你无法使用 const 对象调用它们。请参见我的答案中的函数 f() - Nawaz
显示剩余2条评论

9

实际上,C++标准(即C++ 0x draft)表示(感谢@Xeo和@Ben Voigt向我指出):

23.2.4关联容器
5对于set和multiset,值类型与键类型相同。对于map和multimap,它等于pair。关联容器中的键是不可变的。
6关联容器的迭代器属于双向迭代器类别。对于值类型与键类型相同的关联容器,迭代器和const_iterator都是常量迭代器。未指定iterator和const_iterator是否为同一类型。

因此,VC++ 2008 Dinkumware实现有缺陷。


旧答案:

您之所以收到该错误,是因为在某些 std lib 的实现中,set::iteratorset::const_iterator 是相同的。

例如,libstdc++(随 g++ 一起提供)就有这个问题(请参见此处的整个源代码):

typedef typename _Rep_type::const_iterator            iterator;
typedef typename _Rep_type::const_iterator            const_iterator;

在SGI的文档中,它声明:

iterator       Container  Iterator used to iterate through a set.
const_iterator Container  Const iterator used to iterate through a set. (Iterator and const_iterator are the same type.)

另一方面,VC++ 2008 Express编译您的代码时不会抱怨您在set::iterator上调用非const方法。

5
让我举个更详细的例子。下面是一个结构体示例:
struct Count{
    uint32_t c;

    Count(uint32_t i=0):c(i){}

    uint32_t getCount(){
        return c;
    }

    uint32_t add(const Count& count){
        uint32_t total = c + count.getCount();
        return total;
    }
};

enter image description here

如上所示,IDE(CLion)会提示“在const对象上调用non-const函数'getCount'”。在方法add中,count被声明为const对象,但是getCount方法不是const方法,因此count.getCount()可能会改变count中的成员。

编译错误如下(我的编译器核心消息):

error: passing 'const xy_stl::Count' as 'this' argument discards qualifiers [-fpermissive]

为了解决以上问题,您可以采取以下措施之一:
  1. 将方法uint32_t getCount(){...}改为uint32_t getCount() const {...}。这样,count.getCount()就不会改变count中的成员。
  2. uint32_t add(const Count& count){...}改为uint32_t add(Count& count){...}。这样,count就不必考虑其成员变量的更改。
至于您的问题,std::set中的对象存储为const StudentT,但是getId和getName方法并非const,因此您会遇到上述错误。
您也可以查看Meaning of 'const' last in a function declaration of a class?以获取更多详细信息。

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