它们中有哪些是什么?为什么像'string'和'rope'这样的容器有异常行为?
答案 0 :(得分:3)
正如其他人所说,典型的例子是std::string
。除了锁定多线程程序的性能问题外,引用计数字符串还存在无线程问题。想象一下:
string s = "hello";
string t = s; // s and t share data
char &c = t[0]; // copy made here, since t is non-const
问题是非const operator[]
必须复制字符串(如果它已共享),因为稍后可以使用返回的引用来修改字符串(您可以将其指定给非引用{ {1}},但char
并不知道它的行为应该有所不同。另一方面, const operator[]
应该避免复制,因为这会消除引用计数的所有好处(这意味着你总是复制一个实践)。
operator[]
正如您所看到的,这种区别令人困惑,可能会导致意外行为。
这是一篇关于写时复制语义及其对性能影响的旧文章:http://www.gotw.ca/gotw/045.htm。
以下是有动机将const char &get_first(const string &s) {
return s[0]; // no copy, s is const
}
string s = "hello";
string t = s; // s and t share data
const char &c1 = get_first(t); // no copy made here
const char &c2 = t[0]; // copy made, since t is non-const
// c1 just got invalidated (in fact, it's pointing at s[0], not t[0]).
s[0] = 'X';
printf("%c, %c\n", c1, c2); // outputs "X, h"
更改为在C ++ 11标准中不被引用计数的提案:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2534.html。这就是上面的例子所基于的。
答案 1 :(得分:1)
作为一个示例,一个可能发生在引用计数字符串上的异常,特别是带有“子部分”处理(带有开始/结束切片)的字符串,是“不幸的锁定”。
让我们假设您为文件的整个文本分配内存。然后解析文件并使用一些“slice()”,“left()”,“mid()”或等效方法。您可能会结束锁定文件的整个字符串,而可能只有一小部分包含实际的文本数据(剩余部分已经解析过数字,标点符号或其他内容)。因此,您可能最终使用了更多内存,同时更容易控制峰值使用。在这种情况下,如果您使用多线程并在各种线程中密集使用某些字符串,可能会出现第二个问题:不必要的内存争用,字符串的引用计数可能会增加/减少所有时间和原子性可能会阻碍所有线程的运行。
但是,只要您知道应用程序中的潜在问题并阻止它们(在这种情况下只是通过复制它们来“单独”使字符串),就没有任何反对引用计数的内容。
答案 2 :(得分:1)
作为一般规则,引用计数会受到竞争条件,死锁或过度同步等常见多线程问题的影响。
然后你会遇到通常需要类似闭包行为的上下文问题,即在显然超出范围之后可能需要捕获对象,但这可以通过STL避免,我不是STL专家。
这里有一个讨论,讨论与智能指针相关的各种巴洛克边缘案例:http://www.velocityreviews.com/forums/t689414-c-primer-4th-edition-reference-counting-smart-pointers.html
答案 3 :(得分:1)
在第13项中,Meyers详细阐述了多线程和refcounted字符串的问题。
这在很大程度上取决于std::string
的确切实现以及锁定和使用模式。
如果在多线程环境中对std::string
的明显无害的使用导致由于隐藏锁定而导致延迟,则可能成为问题。循环中这种锁和上下文切换的代价可能很大。但它永远不应该导致死锁。
它不一定是个问题。这本书大于10岁。在此期间,线程实现已得到改进。 Linux-Futexes,例如在大多数情况下表现得更顺畅。
另一点:(由我,dunno,如果迈耶斯也讨论过这个......)
refcounted std::string
表示它具有写时复制语义。这通常是一件好事。实际副本将推迟到实际需要时为止。但这也意味着副本的价格必须在可能难以预测的地方支付。