std::string::c_str() 的结果的生命周期是什么?

118

在我的一个程序中,我必须与一些使用 const char* 的旧代码进行接口。

假设我有一个看起来像这样的结构:

struct Foo
{
  const char* server;
  const char* name;
};

我的高级应用程序只处理 std::string,因此我想使用 std::string::c_str() 获取 const char* 指针。

但是 c_str() 的生命周期是什么?

我能否这样做而不面临未定义的行为?

{
  std::string server = "my_server";
  std::string name = "my_name";

  Foo foo;
  foo.server = server.c_str();
  foo.name = name.c_str();

  // We use foo
  use_foo(foo);

  // Foo is about to be destroyed, before name and server
}

我是不是应该立即将c_str()的结果复制到另一个位置?


当我在函数中定义一个本地字符串并返回.c_str()时,我遇到了这个问题。我不明白为什么有时我只得到字符串的一部分,直到我理解const char*并不永久存在,而是直到字符串被销毁。 - SomethingSomething
7个回答

96

c_str() 的结果会在 std::string 被销毁或调用字符串的非 const 成员函数后失效,因此如果需要保留这个结果,通常需要将其复制一份。

在您的示例中,似乎安全地使用了 c_str() 的结果,因为字符串在该作用域内没有被修改。(但是,我们不知道 use_foo()~Foo() 是否会在这些值上进行拷贝到其他位置的操作,如果它们将字符串复制到别处,则应进行真正的复制,而不仅仅是复制 char 指针。)


如果std::string对象是一个自动对象并且超出了作用域,或者在调用创建线程的函数时,c_str()指针可能无效。 - GuruM
请问您能解释一下什么是“调用字符串的非常量成员函数”吗? - Mathew Kurian
4
“非常量成员函数”是指没有标记const关键字的任何成员函数。这样的函数可能会改变字符串的内容,因此字符串可能需要重新分配内存以便为c_str()返回的空字符结尾版本的字符串提供空间。例如,size()length()const的,所以你可以随意调用它们而不必担心字符串发生变化,但clear()则不是const的。 - Kristopher Johnson

27

从技术上讲,您的代码没问题。

但是,您编写的方式使得那些不了解代码的人容易破坏它。对于c_str(),唯一安全的用法是将其作为参数传递给函数。否则,您会面临维护问题。

示例 1:

{
  std::string server = "my_server";
  std::string name   = "my_name";

  Foo foo;
  foo.server = server.c_str();
  foo.name = name.c_str();

  //
  // Imagine this is a long function
  // Now a maintainer can easily come along and see name and server
  // and would never expect that these values need to be maintained as
  // const values so why not re-use them

  name += "Martin";
  // Oops now its broken.

  // We use foo
  use_foo(foo);

  // Foo is about to be destroyed, before name and server
}

因此,为了维护,请让它显而易见:

更好的解决方案:

{
  // Now they can't be changed.
  std::string const server = "my_server";
  std::string const name   = "my_name";

  Foo foo;
  foo.server = server.c_str();
  foo.name = name.c_str();

  use_foo(foo);    
}

但是如果你有常量字符串,实际上你并不需要它们:
{
  char const* server = "my_server";
  char const* name   = "my_name";

  Foo foo;
  foo.server = server;
  foo.name   = name;

  use_foo(foo);
}

好的。由于某些原因,您希望它们作为字符串:
为什么不仅在调用中使用它们:

{
  std::string server = "my_server";
  std::string name = "my_name";

  // guaranteed not to be modified now!!!     
  use_foo(Foo(server.c_str(), name.c_str());
}

8

此处有效,直到对应的string对象发生以下情况之一:

  • 对象被销毁
  • 对象被修改

只要在将c_str()复制到foo之后但在调用use_foo()之前修改这些string对象,您的代码就不会出现问题。


4

c_str()返回值仅在同一字符串的下一个非常量成员函数调用之前有效。


3
c_str()返回的const char*只有在下一次对std::string对象进行非常量调用之前才有效。在这种情况下,您很好,因为您的std::stringFoo的生命周期内仍然在范围内,并且您在使用foo时没有执行任何其他更改字符串的操作。

2
只要字符串没有被销毁或修改,使用 c_str() 是可以的。如果使用先前返回的 c_str() 修改了字符串,则其实现是不确定的。

2
为了完整起见,这里提供了一个来自cppreference.com的参考和引用
从c_str()获取的指针可能会被以下方式失效:
- 将非const引用传递给任何标准库函数;或者 - 调用string的非const成员函数,但不包括operator[]、at()、front()、back()、begin()、rbegin()、end()和rend()。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接