不止一次,我已经跟踪了一个相当令人困惑的错误,只是发现它是由于const参考参数通过方法改变零件方式.这一直是接收参考参数的方法的结果,该引用参数恰好是引用它自己的数据成员之一的一部分.所以当该方法使该成员变异时,引用(尽管是const)也被突变.
一个简单的例子,我在说什么:
#include <algorithm> #include <iostream> #include <string> #include <vector> class RecentStringBuffer { public: const std::string & at(size_t index) { return v.at(index); } void add(const std::string & s) { // Remove any prevIoUs occurances v.erase(std::remove(v.begin(),v.end(),s),v.end()); // Prepend the new entry v.insert(v.begin(),s); // Truncate older entries v.resize(std::min<size_t>(v.size(),maxEntries)); } private: const int maxEntries = 10; std::vector<std::string> v; }; int main() { RecentStringBuffer r; r.add("A"); // r == [A] r.add("B"); // r == [B,A] r.add("C"); // r == [C,B,A] r.add(r.at(1)); // r == [B,C,A] one would assume? std::cout << r.at(0) << r.at(1) << r.at(2); // Prints "A C A" }
在这个例子中,我们得到了一个令人惊讶的结果,但是如果v已经重新分配了,那么这个引用将被留在v的内存之外,这会更糟.在技术上,参考无论如何都是无效的,所以发生的是未定义的行为.
类似的情况显然会发生在全局变量而不是数据成员和指针而不是引用,但成员和引用通常会更安全,所以这似乎是一个更令人惊讶的陷阱.
现在,我不是问为什么会发生这种情况(我了解发生了什么)或如何解决(有几个明显的方法).我的问题与最佳做法有关:
>这个问题有没有名字?
谁负责担心这个问题?
>或者说另一种方式,错误在哪里在上面的应用程序?当单独查看时,似乎完全合理和有益于at()返回一个const引用和add()来接受一个.另一方面,如果调用者main()也应该更好地知道似乎并不公平,特别是考虑到引用可能会在问题发生之前通过多个函数传递.
>有没有一般的策略来注意和避免这样的结构?
解决方法
通常,函数的默认合同是它应该做它应该做的事情,即使引用参数是指函数可能修改的东西.
标准库的一个例子是:
> std :: vector :: insert(const_iterator pos,InputIt first,InputIt last)被具体记录为说“如果第一个和最后一个是迭代器到* this的行为是未定义的”.
> std :: vector :: insert(const_iterator pos,const T& value)没有这样的文档,因此即使值指向向量的元素,它也必须工作. (这是确认by the committee).