【问题标题】:Construction and initialization of trivial types in C++C++中平凡类型的构造和初始化
【发布时间】:2018-07-15 02:44:01
【问题描述】:

trivial class 可以简单地复制并具有 trivial default constructor(trivial type 是其中之一,或者是类似工作的内置类)。

由于 you can use memcpy 复制普通类型的对象,并且由于 default-initializing 普通类型不会更改表示中的任何字节¹,以下代码(使用 C++20 概念)是否正确初始化了传入的对象?

#include <cstdlib>
#include <cstring>

#include <new>
#include <type_traits>

template <typename T>
T* copy_trivial(T orig) requires std::is_trivial_v<T>
{
    void* buf = std::aligned_alloc(alignof(T), sizeof(T));

    // Note the order of these statements
    std::memcpy(buf, &orig, sizeof(T));
    return new(buf) T;
}

(Compiler Explorer,提供了几个实例化)

看起来这段代码可以工作,因为会有一个具有正确对象表示的初始化对象。但是,考虑到在初始化之后什么都没有发生,那么该对象是否会改为具有indeterminate value²?


¹ 未在单个位置指定,但默认初始化的链接过程调用构造函数,该构造函数必须为implicitly defined;隐式定义的构造函数default-initializes all the members and bases;并且对于内置的平凡类型,该递归在no initialization is performed 处触底。

²[expr.new]p18.1 中的注释说它会,但注释是非规范性的,这似乎仅适用于非放置new。

【问题讨论】:

  • 注意:这个问题的灵感来自this one,但我已经稍微简化了它以避免realloc 的复杂性。
  • 有趣的问题,但我很好奇:以这种方式构造代码有什么好处,而不是先调用placement new,然后是memcpy?
  • @JohnZwinck 样本中的内存是由malloc获得的。如果它是由realloc(或可能用于 C 代码的其他函数)获得的,那么复制将在有机会进行放置 new 之前发生。

标签: c++ initialization language-lawyer


【解决方案1】:

以下代码(使用 C++20 概念)是否正确初始化传入对象的副本?

不,它没有。

它确实返回一个指向有效T 的指针,但标准中没有任何内容要求T 的值是orig 值的副本。

普通类型的默认初始化被称为执行“无初始化”。但这与保留该内存的当前存储不同:[dcl.init]/12

当一个对象获得自动或动态存储时长的存储时,该对象有一个不确定的值,如果没有对该对象执行初始化,该对象将保留一个不确定的值,直到该值被替换。

请注意,它表示它保留了“一个不确定的值”,而不是“与该内存中的相同值”。如果没有这种明确的保护,该标准不需要实现来保留内存的内容。

考虑调试构建。为了捕捉错误,“未执行初始化”的情况有时会用特定字节填充未初始化的内存,以便您可以检测何时访问未初始化的内存。仅当“不确定值”不保留内存中的当前值时,这对于实现才是合法的。

在 C++ 中没有办法用另一个对象的字节副本来初始化一个对象。您可以在任意内存中执行 memcpy,但您不能在该内存中显示一个对象,该对象从该内存中获取其值 来自。并且您可以 memcpy 到现有对象中,但该对象已经被初始化(除非在创建时没有执行初始化)。

所以你能做的最好的就是颠倒这两个语句的顺序。

【讨论】:

  • 我会假设调试版本会“在获得存储 [...] 时”填充未初始化的内存,而不是在每次默认初始化时。它还说“保留”一个不确定的值;在memcpy 之后,我不确定是否还有不确定的值;再次,即在获得存储之后。
  • @DanielH:为对象获取的存储发生在创建对象时。也就是说,作为new 表达式的一部分。仅通过调用malloc 并不能获得对象的存储空间。因此,虽然malloc 可以填写这些值,但放置new 也可以。
  • 看起来“获取存储”没有定义;我假设它大约意味着“分配”,但您的解释也有效(特别是考虑到[expr.new]p8,这可能意味着它可能以其他方式获得存储)。但是,在这种解释下,放置new 从the non-allocating operator new forms 获取存储,这些都指定为“有意不执行其他操作”。更改 ptr 指向的字节将是“其他操作”。
  • 所以显然这是std-discuss 上的一个线程中对本节的共同解释,实际上正在讨论它,并且每个人都接受了放置new 一旦被指出就在这里不起作用对你来说,所以我的解释是错误的,我接受你的回答。至少在那里搜索时,我了解到p0593 和您自己的std::in_place_construct,希望这应该可以减少这个问题。
猜你喜欢
  • 2015-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-03
  • 1970-01-01
  • 2020-09-15
  • 1970-01-01
相关资源
最近更新 更多