真的可以将存储分配与对象初始化分开吗?

Is it really possible to separate storage allocation from object initialization?

来自[basic.life/1]

The lifetime of an object or reference is a runtime property of the object or reference. A variable is said to have vacuous initialization if it is default-initialized and, if it is of class type or a (possibly multi-dimensional) array thereof, that class type has a trivial default constructor. The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • its initialization (if any) is complete (including vacuous initialization) ([dcl.init]),

except that if the object is a union member or subobject thereof, its lifetime only begins if that union member is the initialized member in the union ([dcl.init.aggr], [class.base.init]), or as described in [class.union] and [class.copy.ctor], and except as described in [allocator.members].

来自 [dcl.init.general/1]:

If no initializer is specified for an object, the object is default-initialized.

来自[basic.indet/1]

When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced ([expr.ass]). [Note 1: Objects with static or thread storage duration are zero-initialized, see [basic.start.static]. — end note]

考虑这个 C++ 程序:

int main() {
    int i;
    i = 3;
    return 0;
}

根据C++标准,初始化是在函数main的第一条语句int i;还是第二条语句i = 3;中进行的?

我认为是前者,它对一个不确定的值进行了空洞的初始化,因此开始了对象的生命周期(后者不进行初始化,它对值3进行赋值)。如果是这样,是否真的可以将存储分配与对象初始化分开?

您对为对象分配存储初始化对象感到困惑,它们肯定是不是一样的东西

在您的示例中,对象 i 从未被初始化。作为本地值,它有 space 保留用于存储,但未使用任何值初始化。

第二行的语句赋予一个值3给它。这又不是初始化。

标准要求具有全局存储的对象进行分配和初始化(为零或默认初始化程序所做的任何事情)。所有其他对象仅在书面语言结构支持时才初始化。

C++ allocators 也有同样的区别。 new 运算符在幕后 分配 初始化 对象,之后您可以 分配 对象一个新值。不过,如果需要,您可以使用底层语言结构来分别管理这两件事。

对于大多数用途,您不需要关心代码中对象初始化和赋值之间的区别。如果你到了重要的地步,你要么已经知道这些概念有何不同,要么需要非常快速地学习。

If that is so, is it really possible to separate storage allocation from object initialization?

是:

void *ptr = malloc(sizeof(int));

ptr 指向已分配的存储空间,但没有对象存在于该存储空间中(C++20 表示某些对象 可能 存在,但现在不要介意) .除非我们在那里创建一些对象,否则对象将不存在:

new(ptr) int;

Is initialization performed in the first statement int i; or second statement i = 3; of the function main according to the C++ standard?

第一个。第二条语句是赋值,不是初始化。第二个语句从您对标准的引用中标记“直到该值被替换([expr.ass])”这一点。

If [initialization is the first statement] is so, is it really possible to separate storage allocation from object initialization?

是的,但不是像您这样的简单示例。一个常见的例子是 std::vector。预留容量会分配存储空间 space,但在将对象添加到向量之前不会初始化该存储空间。

std::vector<int> v;   // Allocates and initializes the vector object.
v.reserve(1);         // Ensures space has been allocated for an int object.
/*
 At this point, the first contained element has space allocated, but has
 not yet been initialized. If you want to do nutty things between allocation
 and object initialization, this is the place to do it. Note that you are
 not allowed to access the allocated space since it belongs to the vector.
 You'd have to replicate the inner workings of a vector to do that...
*/
v.emplace_back(3);    // Initializes the first contained object.

引用标准

短版:
没有什么可引用的,因为该标准没有明确禁止所有虚假行为。编译器会根据自己的意愿避免虚假操作。

长版:

严格来说,标准不保证reserve()不初始化任何东西。 [vector.capacity] 中对 reserve() 的要求更侧重于必须做什么,而不是禁止虚假 activity。最接近这个保证的是要求 reserve() 的时间复杂度与容器的大小成线性关系(不是容量,而是当前大小)。这将使得不可能始终初始化所有保留的内容。然而,编译器仍然可以选择初始化固定数量的保留元素,比如最多 1000 万个。只要这个限制是固定的,它就算作恒定时间复杂度,因此 [vector.capacity].

允许

现在让我们开始吧。编译器旨在生成快速代码,而不会引入不必要的、无用的繁忙工作。编译器不会仅仅因为标准不禁止就寻求做额外工作的可能性。除了调试版本,没有编译器会在不需要时引入初始化。认为这种可能性值得考虑的人是忽视大局的语言律师。您无需为不需要的东西付费。这里要问的问题不是“你能引用支持没有初始化happens的标准吗?”而是“你能引用吗支持不需要初始化的标准是必需的?" 由于不需要初始化的额外工作,因此在实践中不会发生。

不过,现实对一些语言律师来说意义不大,这个问题确实有那个标签。为彻底起见,我将证明“可以将存储分配与对象初始化分开”,即使您碰巧使用病态但符合标准且被受虐狂过度设计的编译器。我只需要一个案例来证明“可能”,所以让我们放弃 int,换一种更奇怪但完全合法的类型。

reserve() 的唯一先决条件是所包含的类型可以移动插入到容器中。此前提条件满足以下 class.

class C {
    // Default construction is not supported.
    C() = delete;
  public:
    // Move construction is allowed, even outside this class.
    C(C &&) = default;
};

我把这个 class 设计得很难初始化。唯一允许的构造是移动构造;为了初始化这个类型的对象,你需要已经有一个这个类型的对象。谁创建了第一个对象?没有人。这种类型的对象不能存在。但是,仍然可以创建这些对象的矢量(一个空矢量,但仍然是一个矢量)。

定义 std::vector<C> v; 并随后调用 v.reserve(1); 是合法的。这为 C 类型的对象分配了 space(我的系统需要 1 个字节),但此对象无法初始化。 QED.