【问题标题】:Why VC++ Strings are not reference counted?为什么 VC++ 字符串不被引用计数?
【发布时间】:2009-04-01 19:33:00
【问题描述】:

STL 标准不要求对 std::string 进行引用。但实际上大部分 C++ 实现提供了 refcounted、copy-on-write 字符串,允许你通过 按值字符串作为原始类型。这些实现(至少g ++)也使用 原子操作使这些字符串无锁且线程安全。

简单的测试显示了写时复制语义:

#include <iostream>
#include <string>

using namespace std;

void foo(string s)
{
    cout<<(void*)s.c_str()<<endl;
    string ss=s;
    cout<<(void*)ss.c_str()<<endl;
    char p=ss[0];
    cout<<(void*)ss.c_str()<<endl;
}

int main()
{
    string s="coocko";
    cout<<(void*)s.c_str()<<endl;
    foo(s);
    cout<<(void*)s.c_str()<<endl;
}

在使用非常量成员之后,仅打印两个地址。

我使用 HP、GCC 和 Intel 编译器测试了这段代码,得到了相似的结果——字符串 用作写时复制容器。

另一方面,VC++ 2005 清楚地表明每个字符串都是完全复制的。

为什么?

我知道 VC++6.0 中存在一个错误,该错误具有非线程安全实现 导致随机程序崩溃的引用计数。这是原因吗?他们只是 害怕再使用引用计数,即使这是常见的做法?他们宁愿不 完全使用引用计数来解决问题?

谢谢

【问题讨论】:

    标签: c++ string visual-c++ reference-counting copy-on-write


    【解决方案1】:

    我认为越来越多的 std::string 实现将不再使用 refcounting/copy-on-write,因为它通常是多线程代码中的反优化。

    请参阅 Herb Sutter 的文章 Optimizations That Aren't (In a Multithreaded World)

    【讨论】:

    • 我相信 Scott Meyers 在他的一本书中提到了类似的东西 - 无法引用(甚至不确定),因为我现在没有它们。
    • +1,花了 10 分钟寻找那个参考。有很多关于 SO 的 C++ 问题只需要一个指向 Herb 写作的指针。
    • 其实这篇文章清楚地表明使用原子操作的COW字符串与普通的COW字符串具有几乎相同的性能。 (25% 的差异)所以它不是太多的“开销”,而且对于做一个真正的副本来说显然要多得多。
    • 25%的差异从什么时候变成“几乎一样”了? :)
    • @Andrew 关键是,真正的应对比 COW 贵得多,所以他们比较了线程安全的 COW 和非线程安全的 COW。并且没有太大的区别。超过 25% 的微型基准测试并不算多。
    【解决方案2】:

    STL 实际要求,如果您使用引用计数,则语义与非引用计数版本的语义相同。这对于一般情况来说并非微不足道。(这就是为什么你不应该写你的字符串类)。

    由于以下情况:

    std::string   x("This is a string");
    char&         x5 = x[5];
    std::string   y(x);
    
    x5 = '*';
    

    更多详情请见:http://www.sgi.com/tech/stl/string_discussion.html

    【讨论】:

    • 有趣...该标准实际上有一个注释,特别说明允许引用字符串,但语义必须与非引用字符串相同。 (由于尺寸限制,在下一条评论中继续评论)
    • 标准然后给出了一个类似于你的例子(但通过迭代器而不是引用进行修改)并说第二个字符串不能被修改,但它没有说明如何实现应该这样做(嘿,这是一个实现细节,对吧?)。
    • -1 您实际上可以在 g++ 中检查此代码,并查看它是否完美运行。事实上 g++ 有使用引用计数的线程安全字符串。所以这是绝对可行的
    • 是的,这是可行的,但基本上任何返回左值的函数都必须假设字符串可以被修改并执行 CoW。 (例如 x[5] 返回一个 char&)。这一事实使得引用计数的 std::string 并不是真正的收获。
    • Artyom:-1 是为了什么?他是在谈论语言允许的内容,而不是单个编译器所做的事情。希望您不要天真地相信 C++ 编译器是兼容的? +1 从这里反击 ;)
    【解决方案3】:

    正如 Martin 和 Michael 所说,写时复制 (COW) 往往比它的价值更麻烦,要进一步阅读,请参阅 Kelvin Henney 撰写的这篇关于 Mad COW Disease 的优秀文章,我相信它是Andrei Alexandrescu 指出 Small String Optimization 在许多应用程序中表现更好(但我找不到该文章)。

    小字符串优化是让字符串对象变大并避免小字符串的堆分配。玩具实现看起来像这样:

    class string {
        char *begin_, *end_, *capacity_;
        char buff_[64]; // pick optimal size (or template argument)
    public:
        string(const char* str)
        {
            size_t len = strlen(str);
            if (len < sizeof(buff_))
            {
                strcpy(buff_, str);
                begin_ = buff_;
                capacity_ = buff_ + sizeof(buff_);
            }
            else
            {
                begin_ = strdup(str);
                capacity_ = begin_ + len;
            }
            end_ = begin_+len;
        }
    
        ~string()
        {
            if (begin_ != buff_)
                free(begin_); // strdup requires free 
        }
        // ...
    };
    

    【讨论】:

      【解决方案4】:

      也许微软认为字符串复制不是一个大问题,因为几乎所有 C++ 代码都尽可能使用按引用传递。维护引用计数会产生空间和时间开销(忽略锁定),这可能是他们认为不值得付出的代价。

      也许不是。如果您对此感到担忧,您应该分析您的应用程序以确定字符串复制是否是主要开销,以及是否切换到不同的字符串实现。

      【讨论】:

      • 是的,我同意。只是通过参考。有什么大不了的?想节省打字时间吗?
      【解决方案5】:

      这不是主要原因,但我在win32平台下看到很多不正确的代码,它们执行const_cast&lt; char* &gt;( str.c_str() )之类的操作。

      也许微软知道这一点并关心开发人员:)

      【讨论】:

      • 我以前没有听说过这个,但我敢打赌你是对的,这可能是一个因素。
      猜你喜欢
      • 2016-10-18
      • 2011-07-09
      • 1970-01-01
      • 2011-09-22
      • 1970-01-01
      • 1970-01-01
      • 2017-10-28
      • 2023-02-01
      • 2011-01-13
      相关资源
      最近更新 更多