c++ - 什么时候可以安全地重复使用来自普通可破坏对象的内存而不洗

标签 c++ c++17 unions placement-new type-punning

关于以下代码:

class One { 
public:
  double number{};
};

class Two {
public:
  int integer{};
}

class Mixture {
public:
  double& foo() {
    new (&storage) One{1.0};
    return reinterpret_cast<One*>(&storage)->number;
  }

  int& bar() {
    new (&storage) Two{2};
    return reinterpret_cast<Two*>(&storage)->integer;
  }

  std::aligned_storage_t<8> storage;
};

int main() {
  auto mixture = Mixture{};
  cout << mixture.foo() << endl;
  cout << mixture.bar() << endl;
}

我没有为类型调用析构函数,因为它们很容易被破坏。我对该标准的理解是,为了安全起见,我们需要先清洗存储指针,然后再将其传递给 reinterpret_cast。然而,std::optional 在 libstdc++ 中的实现似乎没有使用 std::launder() 并且只是将对象直接构建到 union 存储中。 https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/optional .

我上面的例子是明确定义的行为吗?我需要做什么才能让它发挥作用? union 会让这项工作成功吗?

最佳答案

在您的代码中,您确实需要 std::launder 以使您的 reinterpret_cast 执行您希望它执行的操作。这是与重用内存不同的问题。根据标准([expr.reinterpret].cast]7),你的表达

reinterpret_cast<One*>(&storage)

相当于:

static_cast<One*>(static_cast<void*>(&storage))

但是,外部 static_cast 没有成功生成指向新创建的 One 对象的指针,因为根据 [expr.static.cast]/13,

if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible (6.9.2) with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.

也就是说,结果指针仍然指向 storage 对象,而不是指向嵌套在其中的 One 对象,并将其用作指向 的指针一个 对象将违反严格的别名规则。您必须使用 std::launder 强制生成的指针指向 One 对象。或者,正如评论中所指出的,您可以直接使用 placement new 返回的指针,而不是从 reinterpret_cast 获得的指针。

如果按照评论中的建议,您使用 union 而不是 aligned_storage

union {
    One one;
    Two two;
};

您将回避指针互换性问题,因此由于非指针互换性,将不需要 std::launder。但是,仍然存在重新使用内存的问题。在这种特殊情况下,由于您的 OneTwo 类不包含任何非-const 限定或引用类型的静态数据成员 ([basic.life]/8)。

最后,问题是为什么 libstdc++ 的 std::optional 实现不使用 std::launder,即使 std::optional 可能包含包含 const 限定或引用类型的非静态数据成员的类。正如评论中指出的那样,libstdc++ 是实现的一部分,当实现者知道 GCC 仍然可以在没有它的情况下正确编译代码时,可以简单地省略 std::launder。导致引入 std::launder 的讨论(参见 CWG 1776 和链接的线程,N4303P0137)似乎表明,在理解的人看来标准比我做的好得多,std::launder 确实是必需的,以便在存在的情况下明确定义 std::optional 的基于 union 的实现const 限定或引用类型的成员。但是,我不确定标准文本是否足够清晰,足以说明这一点,可能值得讨论一下如何澄清这一点。

关于c++ - 什么时候可以安全地重复使用来自普通可破坏对象的内存而不洗,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54581801/

相关文章:

c++ - 大负数乘以 -1 给出输出负数 C++

c++ - 为什么要为具有非平凡析构函数的类声明 constrexpr 构造函数(例如 unique_ptr、std::variant)

c++ - C++ 14或C++ 1z是否已经或将不再不确定以调用委托(delegate)类成员函数指针?

c++ - 实现多类型算术运算符时如何解决 "template argument deduction/substitution failure"

c++ - 将 C++ union 结构转换为 VB6

c++ - 交换单个链表上的节点

c++ - 线程上下文切换

c++ - 不能在嵌套结构中使用 union 元素作为 scanf() 的参数来存储所需的值

c++ - 按值返回和按常量引用传递时避免临时构造

typescript :在将可选类型与必需类型合并时创建并集而不是交集