存储 std::string 数据的指针是否安全?

5

我的问题关于复制构造和重新分配的机制。

我有一个类,用于收集字符串。在向收集中添加字符串后,将复制并存储到向量中。但我也需要访问所有字符串的集合作为 const char * const*,因此我还通过.c_str()存储每个字符串数据的指针。

class MyStrings {
private:
    std::vector<std::string> names;
    std::vector<const char*> cStringPointers;
public:
    const char *const *Data() const
    {
        return this->cStringPointers.data();
    }

    void Add(const std::string &name)
    {
        // copy [name] and store the copy in [this->names].
        this->names.push_back(name); 
        // Store the pointer to the data of the copy.
        this->cStringPointers.push_back(this->names.back().c_str());
    }
}

我知道,将指针存储在向量元素中是不好的,因为当向量重新分配内存时,这些指针将不再有效。
但是我仅存储数据的指针。所以这是我的想法:
如果“names”被调整大小,它将移动构造其中包含的所有字符串,因此这些字符串将不会分配新的内存,而是只使用已经分配的内存,因此我的 cStringPointers 中的指针仍然有效。
我的问题现在很简单:是否有什么我错过的东西,使得这段代码不安全或导致未定义的行为?
(假设我不使用任何异乎寻常的架构或编译器。)

2
为什么要将它们与字符串分开存储?相反,您可以在需要时调用 c_str - 463035818_is_not_a_number
1
我认为OP需要一个指针数组,出于某种原因(例如,要传递给某个C函数)。 - Daniel Langr
你知道记录(名称)的数量吗?如果是,你可以为“names”保留空间,这样就不会发生重新分配。另一种选择是检查重新分配(比较“names”的大小和容量),如果发生了重新分配,则重建整个数组“cStringPointers”。 - Daniel Langr
是的,@DanielLangr是正确的,我需要用于某些C接口,更具体地说是用于Vulkan,因为它要求扩展和层名称为const char * const*。 - Lukas-T
4个回答

11
我的问题现在很简单:我是否错过了任何可能使这段代码不安全或导致未定义行为的内容?
是的,您错过了小字符串优化。这是标准允许的并得到广泛实现,会导致指针悬空,因为字符串实际上将它们的数据移动到新位置。

谢谢,我之前不知道这个 :) - Lukas-T

4

这是不安全的。即使 cStringPointers 也不安全。

值得注意的是,大多数编译器的标准库都实现了一种称为Small String Optimization (SSO)的东西。在SSO中,如果字符串很小(在GCC中为15个字符),则该字符串的内存不会在堆中分配,而是直接保存在std::basic_string类中。为了实现这一点,std::basic_string的大小要大于指针所需的大小(begin、end、capacity)。

这意味着,如果向量被重新定位,小字符串将改变它们的位置。 较长的字符串将保持有效,因为它们是在堆上分配的,不会被复制。


2
我的问题很简单:我是否遗漏了一些可能会导致代码不安全或导致未定义行为的内容?
是的。即使任何常见的std::string实现会移动字符串的数据并保持指针有效,但这个特定的假设取决于实现,因此是未定义行为。只有当标准实际上保证这样的细节时,您才可以依赖它(通常在标题为“迭代器有效性”等的部分中找到)。在std::string的移动构造函数(No. 2)的文档中,它明确说明:

与其他容器移动赋值不同,对str的引用、指针和迭代器可能无效。

这里,对于大多数实现来说,这个假设实际上是错误的,因为它们使用了小字符串优化。这将把长度不超过一定大小(“小字符串”)的字符串存储在string对象本身中,而不是动态分配内存。因此,当string被移动时,它只能避免复制动态分配的长字符串,而实际上会复制小字符串。因此,在移动小字符串后,c_str()将产生不同的指针。

2
只是从C++标准[string.require.4]中添加一个相关的引用:
引用、指针和迭代器指向basic_string序列的元素,可能会被该basic_string对象的以下使用使无效:
- 作为传递给任何以非const basic_string作为参数的引用的标准库函数的参数。
在向量重新分配期间对字符串进行移动构造正是这种情况,因为移动构造函数将非const字符串的引用作为参数。

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