【问题标题】:Which string classes to use in C++?在 C++ 中使用哪些字符串类?
【发布时间】:2011-06-10 12:18:47
【问题描述】:

我们有一个 C++ (MFC) 中的多线程桌面应用程序。目前开发人员使用 CString 或 std::string,可能取决于他们的心情。所以我们想选择一个实现(可能不是这两个)。

MFC 的 CString 基于写时复制 (COW) 习惯用法,有些人会声称这在多线程环境中是不可接受的(并且可能引用 this article)。我不相信这样的说法,因为原子计数器似乎相当快,而且这种开销通过减少内存重新分配以某种方式得到补偿。

我了解到 std::string 的实现依赖于编译器——它不是 MSVC 中的 COW,但它是,或者曾经在 gcc 中。据我了解,新的 C++0x 标准将通过要求非 COW 实现来解决此问题,并解决一些其他问题,例如连续缓冲区要求。所以实际上 std::string 在这一点上看起来没有很好的定义......

我不喜欢 std::string 的一个简单示例:如果没有过多的重新分配,就无法从函数返回字符串(如果按值返回,则复制构造函数,并且无法访问内部缓冲区来优化它所以“通过引用返回”例如std::string& Result 没有帮助)。我可以通过按值返回(由于 COW 没有复制)或通过引用传递并直接访问缓冲区来使用 CString 执行此操作。同样,C++0x 以其右值引用来拯救,但我们不会在最近的特征中使用 C++0x。

我们应该使用哪个字符串类? COW真的会成为问题吗?还有其他常用的有效字符串实现吗?谢谢。

编辑:我们目前不使用 unicode,我们不太可能需要它。但是,如果有一些东西可以轻松支持 unicode(而不是以 ICU 为代价...),那将是一个加分项。

【问题讨论】:

  • 你知道RVO吗?这可能会减轻您对字符串作为返回值的一些担忧。您可能还想阅读Want Speed? Pass By Value.
  • 如果您想要未来的证明,请选择 std::string。此外,它快速、防弹且支持极好。
  • C++0x 不允许 std::basic_string 的 COW 实现。它不允许非连续缓冲区实现,例如 SGI 的“绳索”。
  • @Billy:你确定吗?我见过有人声称它确实不允许 COW,比如这里:stackoverflow.com/questions/4496150/…
  • @7vies:在 C++03 中,标准委员会确保竭尽全力确保 COW 是 std::string 的有效实现。但是,这不是必需的。

标签: c++ multithreading string copy-on-write


【解决方案1】:

我会使用std::string

  • 促进与 MFC 的解耦
  • 与现有 C++ 库更好地交互

“按价值回报”问题主要是非问题。编译器非常擅长执行 返回值优化 (RVO),这实际上在大多数情况下在按值返回时消除了副本。如果没有,您通常可以调整该功能。

COW 被拒绝是有原因的:它不能扩展(很好),并且没有真正衡量过所希望的速度增加(参见 Herb Sutter 的 article)。原子操作并不像看起来那么便宜。使用单处理器单核很容易,但现在多核已成为商品,多处理器已广泛使用(用于服务器)。在这样的分布式架构中,有多个缓存需要同步,架构越分布式,原子操作的成本就越高。

CString 是否实现了小字符串优化?这是一个简单的技巧,允许字符串不为小字符串(通常是几个字符)分配任何内存。非常有用,因为事实证明大多数字符串实际上都很小,您的应用程序中有多少字符串长度小于 8 个字符?

因此,除非您向我展示一个真正的基准,清楚地显示使用 CString 的净收益,否则我宁愿坚持使用该标准:它是标准的,并且可能会得到更好的优化。

【讨论】:

  • "与现有 C++ 库更好的交互" - 如果现有 C++ 库是 MFC,CString 提供更好的交互。
  • @7vies: SSO 与 COW 完美兼容,但我从未见过 CString 的内在所以我不知道它是否使用它。我已尝试回答您的问题:我已经解决了您的性能问题,并且我已尝试明确表示,如果在标准版本和某些自定义版本之间进行选择,除非存在性能问题,否则我会选择标准版本。跨度>
  • @Mark Ransom:但是会只使用 MFC 吗?我知道我在 Boost 之上可能使用了 7 或 8 个免费库,但他们都不知道 MFC 是什么。而且我在一家大公司,很多底层的东西都在这里重新设计,所以我猜如果公司更小,我会使用更多的库。
  • @7vies:它是标准,因为它是标准库的一部分,因此可供所有开发人员使用。现在我承认您的特定环境对我来说是未知的,但我仍然会警惕将自己限制在 MFC 特定的库中。一个例子:TinyCpp(针对 TinyXml 的 cpp 适配)使用 std::string
  • @7vies: 像所有优化一样,它只有在编译器支持它们时才有效:) 虽然它很标准,但我很难猜测它是什么时候在 MSVC 中引入的(我只使用过它从 2008 年起)。我确实记得在 2008 年的 NRVO 中没有在调试版本中执行。
【解决方案2】:

实际上,答案可能是“视情况而定”。但是,如果您使用的是 MFC,恕我直言,使用 CString 会更好。此外,您还可以将 CString 与 STL 容器一起使用。但是,这会导致另一个问题,我应该使用带有 CString 的 stl 容器还是 MFC 容器?使用 CString 将为您的应用程序提供敏捷性,例如在 unicode 转换中。

编辑:此外,如果您使用 WIN32 api 调用,CString 转换会更容易。

编辑:CString 有一个 GetBuffer() 和允许您直接修改缓冲区的方法。

编辑:我在我们的 SQLite 包装器中使用了 CString,格式化 CString 更容易。

    bool RS::getString(int idx, CString& a_value) {

//bla bla

        if(getDB()->getEncoding() == IDatabase::UTF8){
            a_value.Format(_T("%s"), sqlite3_column_text(getCommand()->getStatement(), idx));
        }else{
            a_value.Format(_T("%s"), sqlite3_column_text16(getCommand()->getStatement(), idx));
        }
        return true;
}

【讨论】:

  • 我们绝对不会使用 MFC 容器 :) 我们已经使用 STL 了
  • @Billy:你怎么能同时提到 SGI 的绳索和 &MyStdString[0]? (对于每个人来说,它还不是 C++0x,对吧?)我也不同意 std::stringstream,因为它是一种非常漂亮的字符串格式化方式,尤其是在修饰符方面。 Boost::format 看起来好多了
  • @7vies: Because nobody implements std::string in terms of ropes. 我同意 boost::format 可以更好,但不是所有人都可以使用 boost。
  • @Billy:很好的参考,谢谢。在没有 boost:format 的情况下,许多人不会坚持使用 stringstream,而是会继续使用 C 风格的 printf 等,我认为这是合理的,因为看到 stringstream 有多么不方便
  • @7vies:我的意思是我们的意见分歧并不重要。我对 stringstream 非常满意。你不是。但我们的意见在这里都不重要,因为问题是关于字符串,而不是格式。
【解决方案3】:

我不知道任何其他常见的字符串实现——它们都受到 C++03 中相同的语言限制。要么他们提供一些特定的东西,比如 ICU 组件如何非常适合 Unicode,他们真的像 CString 一样老,要么 std::string 胜过他们。

但是,您可以使用 MSVC9 SP1 STL 使用的相同技术,即“swaptimization”,这是有史以来最有趣的优化。

void func(std::string& ref) {
    std::string retval;
    // ...
    std::swap(ref, retval); // No copying done here.
}

如果您滚动了一个自定义字符串类,它没有在其默认构造函数中分配任何内容(或检查您的 STL 实现),那么交换优化将保证没有多余的分配。例如,我的 MSVC STL 使用 SSO,默认情况下不分配任何堆内存,因此通过对上述内容进行交换优化,我不会获得冗余分配。

您也可以通过不使用昂贵的堆分配来显着提高性能。有一些分配器是为临时分配而设计的,您可以将您最喜欢的 STL 实现中使用的分配器替换为自定义的分配器。您可以从 Boost 中获取对象池之类的东西,或者滚动内存领域。与正常的新分配相比,您可以获得十倍的性能。

【讨论】:

    【解决方案4】:

    我建议做出“每个 DLL”的决定。如果您的 DLL 严重依赖于 MFC(例如,您的 GUI 层),您需要大量带有 CString 参数的 MFC 调用,请使用 CString。如果您的 DLL 中您要使用的唯一来自 MFC 的东西就是 CString 类,请改用 std::string。当然,你需要两个类之间的转换函数,但我怀疑你已经解决了这个问题。

    【讨论】:

      【解决方案5】:

      我说总是选择std::string。如前所述,RVO 和 NVRO 将使通过副本返回变得便宜,并且当您最终确实最终切换到 C++0x 时,您可以从移动语义中获得不错的性能提升,而无需执行任何操作。如果您想获取任何代码并在非 ATL/MFC 项目中使用它,则不能使用 CString,但 std::string 将在那里,因此您将拥有更轻松的时间。最后,您在评论中提到您使用 STL 容器而不是 MFC 容器(好举措)。为什么不保持一致并使用 STL 字符串呢?

      【讨论】:

      • MFC 容器无法使用,所以这很简单:) 另一方面,CString 使用起来非常舒服,正如其他答案中提到的那样 - 例如,CString::Format 是非常有用(我知道有一些方法可以用 std::string 做类似的事情,但它们远没有那么方便)。
      【解决方案6】:

      我建议使用 std::basic_string 作为您的通用字符串模板库,除非有充分的理由不这样做。我说 basic_string 是因为如果你要处理 16 位字符,你会使用 wstring。

      如果您打算使用 TCHAR,您可能应该将 tstring 定义为 basic_string,并且可能希望也为其实现一个特征类以使用 _tcslen 等函数。

      【讨论】:

        【解决方案7】:

        std::string 通常是引用计数的,所以按值传递仍然是一种廉价的操作(对于 C++0x 中的右值引用更是如此)。 COW 仅针对具有多个指向它们的引用的字符串触发,即:

        std::string foo("foo");
        std::string bar(foo);
        foo[0] = 'm';
        

        将通过 COW 路径。由于 COW 发生在 operator[] 内部,您可以通过使用其(非常量)operator[]()begin() 方法强制字符串使用私有缓冲区。

        【讨论】:

        • 据我所知,MSVC中没有引用计数,如果我错了,请纠正我。我们也被旧的 MSVC 版本卡住了。
        • 其实C++0x标准是禁止使用COW的。
        • @Matthieu:你有这方面的标准参考吗? (不是说你错了,就是找不到)
        • @Billy:我仔细看了看,但在 n3225 中也找不到任何东西。我唯一能找到的是 [valarray.cons] 中的valarray:备注 289,允许数组共享存储的实现,但它们应实现引用时复制确保数组在概念上不同的机制 我很确定 COW 被禁止用于字符串,但它可能源于其他讨论......
        • stackoverflow.com/q/12199710/845092 澄清了“仅允许 [特定函数] 使迭代器/引用无效”这使得执行 COW 是非法的,因为非常量 [] 不在列表中。然而,GCC 故意忽略了这一点以保留他们的 ABI。 stackoverflow.com/q/12520192/845092
        猜你喜欢
        • 1970-01-01
        • 2014-07-25
        • 2010-09-23
        • 1970-01-01
        • 2022-01-21
        • 1970-01-01
        • 2014-03-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多