我不止一次追踪到一个相当令人费解的错误,结果发现它是由于 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 )
专门记录为“如果first
和last
是*this
的迭代器。std::vector::insert( const_iterator pos, const T& value )
没有这样的文档,因此即使value
引用了 vector 。 (已确认 by the committee )。
因此,在您的代码中,即使 s
引用 v 的成员,您也需要修改 add()
才能工作;或记录它不起作用。
关于c++ - 如何处理可能指向内部数据的引用参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43458465/