我有一个班级:
class A {
public:
string B;
};
然后是代码:
A a1;
a1.B = "abc";
printf("%p.\n", a1.B.c_str());
A a2(a1);
printf("%p.\n", a2.B.c_str());
两个实例的c_str都指向同一个地方(据我所知,复制构造函数逐位复制,字符串在内部将数据存储在char *中,指针被复制。
但问题是,为什么这段代码没有崩溃? a1和a2是堆栈变量,当解构它们时,字符串B也将被解构,赢得那些字符串的内部字符*(指向相同的内存位置)被删除两次?是不是双重删除,这应该导致崩溃?顺便说一句,我禁用了gcc优化,而valgrind也没有显示任何内容。
答案 0 :(得分:13)
不,指针不被复制。 std::string
的拷贝构造函数创建了一个 new 缓冲区,并从另一个字符串的缓冲区中复制数据。
编辑:C ++标准使用来允许写时复制语义,它将共享指针(并且需要引用计数与它一起),但这是从C开始不允许的++ 11。显然有GCC版本可以做到这一点。
答案 1 :(得分:3)
对于GCC 4。*
字符串类中有一个内部计数器,用于了解指向缓冲区的实例数。当计数器变为0时,实例负责释放内存。它与共享指针(boost或C ++ 11)的行为相同。
此外,在修改字符串时,会分配一个新的缓冲区,以避免在共享缓冲区的其他实例上进行修改。
答案 2 :(得分:2)
应该崩溃但不会
这句话应该带有一点点。 C ++没有“必须崩溃”的概念。它有一个未定义行为的概念,可能会也可能不会导致崩溃。即便如此,您的代码也没有未定义的行为。
两个实例的c_str都指向同一个地方(我理解,复制 构造函数逐位复制,字符串在内部存储数据 char *,指针被复制。
您正在谈论std::string
的实施。您必须改为查看其接口,以确定哪些操作是安全的,哪些操作不安全。
除此之外,您正在谈论的实现,称为copy-on-write或“COW”,is obsolete since C++11。最新的GCC版本已经放弃了它。
请参阅GCC 5 Changes, New Features, and Fixes:
默认启用
std::string
的新实现,使用 小字符串优化而不是 copy-on-write 引用计数。
小字符串优化与使用的技术相同,例如,在std::string
的Visual C ++实现中。它以完全不同的方式工作,因此如果您使用足够新的GCC版本,那么您对std::string
如何在内部工作的理解不再正确,或者如果您使用Visual C ++则从未正确。
但问题是,为什么这段代码不会崩溃?
因为它根据其接口的文档正确使用std::string
操作,并且因为您的编译器没有完全破坏。
您基本上是在问为什么编译器会为正确的代码生成一个工作二进制文件。
a1和a2是堆栈变量,
是(正确的术语是对象具有“自动存储持续时间”)。
当解构它们时,字符串B也将被解构,那些字符串的内部字符*(指向相同的内存位置)是否会被删除两次?
您的编译器的std::string
实现确保不会发生这种情况。它根本不使用COW,或者析构函数包含检查共享缓冲区是否已被删除的代码。
如果您使用的是较旧的GCC版本,那么您只需查看std::string
实施的源代码即可了解其完成情况。毕竟它是开源的 - 但要注意,因为它可能看起来有点可怕。例如,这是an older GCC version的析构函数代码:
~basic_string() { _M_rep()->_M_dispose(this->get_allocator()); }
然后查看_M_dispose
(在同一个文件中),您将看到它是一个非常复杂的实现,具有各种检查和同步。
还要考虑这个:
如果复制std::string
的纯粹行为会导致崩溃,那么整个课程将毫无意义,不是吗?
答案 3 :(得分:-2)
它不会崩溃,因为字符串复制实际上会复制字符串,因此两个字符串都将指向具有相同数据的不同内存位置。