【问题标题】:Is it safe to store the pointer to the data of a std::string?存储指向 std::string 数据的指针是否安全?
【发布时间】:2019-08-30 09:22:27
【问题描述】:

我的问题围绕着复制构造和重新分配的机制。

我有一个收集字符串的类。将字符串添加到集合后,该字符串被复制并存储在向量中。但由于我还需要以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 中的指针仍然有效.

我现在的问题很简单:我是否遗漏了一些会使此代码不安全或导致未定义行为的东西?

(假设我不使用任何奇特的架构和/或编译器。)

【问题讨论】:

  • 为什么将它们与字符串分开存储?相反,您可以在需要时致电c_str
  • @foreknownas_463035818 我相信 OP 出于某种原因需要一个指针数组(例如,要传递给某个 C 函数)。
  • 你提前知道记录(名字)的数量吗?如果是,您可以为names 预留空间,然后就不会发生重新分配。另一种选择是检查重新分配(比较names 的大小和容量),如果发生,重建整个数组cStringPointers
  • 是的,@DanielLangr 是对的,我需要一些 C 接口,更具体地用于 Vulkan,因为它需要扩展名和层名作为 const char * const*。

标签: c++


【解决方案1】:

我现在的问题很简单:我是否错过了一些可以使 此代码不安全或导致未定义的行为?

是的:您错过了小字符串优化。它是标准允许的并被广泛实施,当字符串实际上将它们的数据移动到它们的新位置时,会导致指针悬空。

【讨论】:

  • 谢谢,我不知道这个:)
【解决方案2】:

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

注意,大多数编译器的标准库都实现了一种叫做:小字符串优化 (SSO) 的东西。基本上在 SSO 中,如果字符串很小(在 gcc 中为 15 个字符),则该字符串的内存不会在堆中分配,而是直接保存在 std::basic_string 类中。要实现 std::basic_string 大于指针所需的大小(开始、结束、容量)。

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

【讨论】:

    【解决方案3】:

    我现在的问题很简单:我是否遗漏了一些会使这段代码不安全或导致未定义行为的东西?

    是的。这个特定的假设取决于实现,因此 UB 即使std::string 的任何常见实现 移动字符串的数据并保持指针有效。只有当这样一个细节真正得到标准的保证时,你才能依赖它。 (常见于标题为“迭代器有效性”等的部分。)在documentation of std::string's move constructor(第 2 号)中明确指出:

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

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

    【讨论】:

      【解决方案4】:

      只需添加来自 C++ 标准 [string.require.4] 的相关引用:

      引用basic_­string 序列的元素的引用、指针和迭代器可能会因basic_­string 对象的以下用途而失效

      作为参数传递给任何标准库函数,将非常量 basic_­string 的引用作为参数。

      向量重新分配期间字符串的移动构造正是这种情况,因为移动构造函数将非常量字符串的引用作为参数。

      【讨论】:

        猜你喜欢
        • 2017-05-09
        • 1970-01-01
        • 2016-05-28
        • 1970-01-01
        • 1970-01-01
        • 2012-12-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多