如果我们考虑使用引用计数的std :: string实现,请考虑以下情况:
int main()
{
string english = "Hello";
string german = english; //refcnt = 2
string german2 = german;
/* L1 */ german[1] = 'a';
/* L2 */ *(german2.begin() + 1) = 'A';
cout << english << endl << german << endl << german2 << endl;
return 0;
}
L1和L2会发生什么?引用计数是否已损坏并执行了深层复制?我是这么认为的,但我担心如果发生这种情况,那就做一个简单的事了:
cout << german[1] << endl;
或简单:
cout << *(german.begin()) << endl;
非const上下文中的会执行不必要的深层复制。我对吗?这些实现如何处理这个细节?
答案 0 :(得分:6)
你是对的,在所有四个例子(L1,L2和下面两个例子)中都会有一个副本,即使对于后两个例子也是如此。
不幸的是,当调用operator []的非const版本或取消引用非const迭代器时,实现无法判断生成的非const引用是否将用于修改对象,所以它必须安全地播放并制作副本。
C ++ 11向字符串和其他容器添加了函数cbegin()
和cend()
,这些容器即使在非const对象上调用也会返回const迭代器。这有助于缓解这个问题。我不知道运营商[]的可比解决方案。
注意:让operator []或迭代器的运算符*()返回一个代理类型,正如其他一些回答者建议的那样,实际上不是一个选项,因为它打破了容器需求,其中一个是这些函数返回实际的引用。 (这就是为什么现在每个人都同意vector<bool>
是一个错误 - 它以这种方式使用代理。)
(当然,如果您正在编写自己的引用计数类,那么就没有什么能阻止您使用代理类型来实现此目的。)
答案 1 :(得分:2)
实现这一目标的一种方法是通过代理类。所以当你索引到一个字符串而不是获得一个字符时,你会得到一个外观和感觉像char的对象。当对其执行写入时,它会导致原始字符串的深层复制。