如何处理可能指向内部数据的引用参数?

Par*_*tes 9 c++ reference

我不止一次追踪到一个相当令人困惑的错误,却发现它是由于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"
}
Run Code Online (Sandbox Code Playgroud)

在这个例子中,我们得到了一个令人惊讶的结果,但是如果v已经重新分配,那么引用将被指向外部v的内存,这本来会更糟糕.从技术上讲,引用无论如何都是无效的,所以发生的事情是未定义的行为.

类似的情况显然可能发生在全局变量而不是数据成员和指针而不是引用,但成员和引用通常感觉更安全,所以这似乎是一个更令人惊讶的陷阱.

现在,我不是在问为什么会发生这种情况(我明白发生了什么)或者如何解决它(有几种明显的方法).我的问题与最佳做法有关:

  • 这个问题有名字吗?
  • 谁有责任担心这个问题?
  • 或者说另一种方式,上述应用程序中的错误在哪里?单独查看时,at()返回const引用和add()接受引用似乎是完全合理和有益的.另一方面,似乎公平地说调用者main()应该更好地知道,特别是考虑到在问题发生之前可以通过多个函数向下传递引用.
  • 有没有注意和避免这种结构的一般策略?

M.M*_*M.M 8

通常,函数的隐含契约是,即使引用参数引用函数可能修改的内容,它也应该执行它应该执行的操作.

如果函数不支持这个,那么应该清楚地记录它.

标准库中的一个示例是:

  • std::vector::insert( const_iterator pos, InputIt first, InputIt last ) 具体记录说" 行为是未定义的,如果first并且last是迭代器*this ".
  • std::vector::insert( const_iterator pos, const T& value )没有这样的文档,因此它必须工作,即使value引用向量的元素.(这得到了委员会的确认).

因此,在您的代码中add(),即使s引用v的成员,您也需要修改才能工作; 或证明它不起作用.