【问题标题】:string not optimized enough for string literals字符串没有针对字符串文字进行足够优化
【发布时间】:2012-01-13 05:26:05
【问题描述】:

在我的一个 C++ 项目中,我在将所有 char* 替换为 std::string 之前迈出了一步,但我发现有一个特定的场合 std::string 惨遭失败。

想象一下我有这两个功能:

void foo1(const std::string& s)
{
    ...
}

void foo2(const char* s)
{
    ...
}

如果我这样写:

const char* SL = "Hello to all!";

foo1(SL); // calls malloc, memcpy, free
foo2(SL);

foo1 中,SL 将隐式转换为std::string。这意味着std::string 构造函数将分配内存并将字符串文字复制到该缓冲区。在foo2 尽管所有这些都不会发生。

在大多数实现中,std::string 应该是超级优化的(例如,写时复制),但是当我用 const char* 构造它时,它不是。我的问题是:为什么会这样?我错过了什么吗?我的标准库是否优化得不够充分,或者出于某种原因(我不知道)这完全不安全?

【问题讨论】:

  • 写时复制并不是真正的“超级优化”。我相信 GCC 的标准库仍然使用它,但这只是因为它在 10 年前有意义,那时多线程还没有成为常态。今天实现的一个理智的库实现将避免像瘟疫一样的 COW。
  • 我认为标准甚至不允许 COW,因为成员函数的迭代器失效要求。
  • Afaik C++03 允许 COW。我相信 C++11 禁止它

标签: c++ string stl


【解决方案1】:

实际上,如果您更改文字,您的担忧就会消失(*):

std::string const SL = "Hello to all!";

我为你添加了const

现在,调用foo1 将不涉及任何复制(根本),调用foo2 可以以很小的成本实现:

foo1(SL);         // by const-reference, exact same cost than a pointer
foo2(SL.c_str()); // simple pointer

如果你想移动到std::string,不要只切换函数接口,还要切换变量(和常量)。

(*) 最初的答案假定 SL 是一个全局常量,如果它是函数的局部变量,那么如果确实希望避免在每次调用时都构建它,则可以将其设为 static

【讨论】:

  • 另外一点:如果字符串文字在函数中,您可能希望将其设为静态。
  • 这不是意味着你所有的字符串文字都会在启动时被复制到堆中吗?
  • 为什么忧虑会消失?现在,std::string 对象将在进入/退出范围内构造/销毁,这可能导致与以前相同的内存分配/释放(取决于 std::string 实现)。将其设为 const 不会使其成为静态的,不是吗?
  • @7vies:确切地说,我原本以为SL 是一个全局常量。 James 已经指出,如果它在函数中,它也应该是静态的。
  • @Matthieu:有趣,出于某种奇怪的原因,我的大脑过滤掉了 James 的评论 :) 不过,您可以在回答中提及“静态”,以避免人们想知道为什么担心会消失跨度>
【解决方案2】:

std::string 不是灵丹妙药。它旨在成为拥有其内存的通用可变字符串的最佳实现,并且与 C API 一起使用相当便宜。这些是常见的场景,但它们并不匹配每个字符串使用实例。

正如您所提到的,字符串文字不太适合这个用例。他们使用静态分配的内存,所以std::string 不能也不应该尝试获取内存的所有权。而且这些字符串始终是只读的,所以std::string 不允许你修改它们。

std::string 创建传递给它的字符串数据的副本,然后在内部处理此副本。

如果您想操作其生命周期在其他地方处理的常量字符串(在字符串文字的情况下,它由初始化和释放静态数据的运行时库处理),那么您可能需要使用不同的字符串表示。也许只是一个简单的const char*

【讨论】:

  • 这是拥有其内存的可变字符串的最佳实现。 不。差远了。 .c_str() 成本提出的严格要求。当字符串很大并被修改时,最好的实现可能会使用 B-Trees 来避免所有那些代价高昂的重新分配。
  • 但它会失去连续性,这会使转换为 C 字符串的成本更高。有很多权衡需要考虑。 :) 但我澄清了一点我的答案。
  • 是的,这就是我提到c_str的原因。我相信 SGI STL 有一个 rope 类来涵盖不需要 C 交互的情况。
  • @Matthieu M.:确实在 SGI 中有一个 std::rope。但是,stringrope 有不同的用例:第一个更适合某些用例(快速迭代器、快速追加),后者更适合其他一些用例(想想应用程序的文本编辑器,但在迭代时会失败)。 最佳对于这个问题是非常主观的。
  • @Matthie M.:是的,rope 是一个重型string。得到它? ;-)
【解决方案3】:

问题在于 std::string 类无法识别 const char* 指针是否为全局字符文字:

const char *a = "Hello World";
const char *b = new char[20];

char* 指针可能在任何时候变得无效(例如当它是一个局部变量并且函数/范围结束时),因此std::string 必须成为字符串的独占所有者。这只能通过复制来实现。

下面的例子说明了为什么它是必要的:

std::string getHelloWorld()  {
  char *hello = new char[64];
  strcpy(hello, "Hello World");
  std::string result = (const char *)hello;  // If std::string didn't make a copy, the result could be a garbage
  delete[] hello;
  return result;
}

【讨论】:

  • 实际上,字符串字面量是 char[N] 其中 N 是长度 + 1(空终止符)。
  • 为什么是 new ?在堆栈上分配缓冲区也可以:char const hello[] = "Hello World";
  • @MatthieuM.: 你的版本是异常安全的,而 dark_charlie 的不是因为字符串构造函数不是 no-throw。
  • new和delete比较醒目,我觉得更能说明有问题。在堆栈上分配缓冲区也是未定义的行为,但实际上它很可能会起作用,因为编译器分配字符串文字的方式。
  • 这就是我所缺少的,也是我正在寻找的答案
猜你喜欢
  • 1970-01-01
  • 2017-01-17
  • 2012-11-10
  • 1970-01-01
  • 2017-12-09
  • 1970-01-01
  • 2013-08-02
  • 2015-07-06
  • 2012-07-09
相关资源
最近更新 更多