17

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

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

我了解到 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 为代价......),那将是一个加号。

4

7 回答 7

16

我会用std::string.

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

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

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

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

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

于 2011-01-17T15:25:05.787 回答
5

实际上,答案可能是“视情况而定”。但是,如果您使用的是 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;
}
于 2011-01-17T15:17:42.497 回答
1

我不知道任何其他常见的字符串实现——它们都受到 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 中获取对象池之类的东西或滚动内存领域。与正常的新分配相比,您可以获得十倍的性能。

于 2011-01-17T15:19:45.377 回答
1

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

于 2011-01-17T17:03:39.317 回答
1

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

于 2011-01-17T18:12:00.477 回答
0

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

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

于 2011-01-17T15:17:37.413 回答
-2

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

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

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

于 2011-01-17T15:23:28.953 回答