初始化对象

时间:2018-03-30 05:25:16

标签: c++ initialization compiler-optimization placement-new

这个问题来自this帖子中的评论部分,并且在那里也得到了答案。但是,我认为仅仅留在评论部分是非常重要的。所以我为它做了这个Q& A.

Placement new可用于初始化已分配存储空间的对象,例如

using vec_t = std::vector<int>;
auto p = (vec_t*)operator new(sizeof(vec_t));
new(p) vec_t{1, 2, 3}; // initialize a vec_t at p

根据cppref

  

展示新

     

如果提供了placement_params,它们将作为附加参数传递给分配函数。在标准分配函数void* operator new(std::size_t, void*)之后,这样的分配函数被称为&#34; placement new&#34;,仅仅返回其第二个参数。这用于在已分配的存储[...]

中构造对象

这意味着new(p) vec_t{1, 2, 3}只返回pp = new(p) vec_t{1, 2, 3}看起来多余。忽略返回值真的可以吗?

1 个答案:

答案 0 :(得分:7)

无论是迂腐还是实际,忽略返回值都不行。

从迂腐的角度来看

对于p = new(p) T{...}p有资格作为指向由new表达式创建的对象的指针,尽管该值相同,但该表达式不适用于new(p) T{...}。在后一种情况下,它只能作为指向已分配存储的指针。

非分配全局分配函数返回其参数而没有隐含的副作用,但是new-expression(placement或not)总是返回指向它创建的对象的指针,即使它碰巧使用了该分配函数。 / p>

根据cppref关于delete-expression(强调我的)的描述:

  

对于第一个(非数组)表单,表达式必须是指向对象类型的指针或上下文隐式可转换为此类指针的类类型,其值必须为< em> null 或指向由new-expression创建的非数组对象的指针,或指向由new-expression创建的非数组对象的基础子对象的指针。如果表达式是其他任何东西,包括它是否是由new-expression的数组形式获得的指针,行为是未定义的。

因此p = new(p) T{...}失败导致delete p未定义的行为。

从实际角度来看

从技术上讲,没有p = new(p) T{...}p不会指向新初始化的T,尽管值(内存地址)是相同的。因此,编译器可能会认为p仍然引用了新的展示位置之前的T。考虑代码

p = new(p) T{...} // (1)
...
new(p) T{...} // (2)

即使在(2)之后,编译器也可能认为p仍然引用在(1)初始化的旧值,从而进行不正确的优化。例如,如果T有一个const成员,编译器可能会将其值缓存在(1),并且即使在(2)之后仍然使用它。

p = new(p) T{...}有效地禁止了这种假设。另一种方法是使用std::launder(),但只需将展示位置的返回值分配回p即可更轻松,更清晰。

你可以做些什么来避免陷阱

template <typename T, typename... Us>
void init(T*& p, Us&&... us) {
  p = new(p) T(std::forward<Us>(us)...);
}

template <typename T, typename... Us>
void list_init(T*& p, Us&&... us) {
  p = new(p) T{std::forward<Us>(us)...};
}

这些功能模板始终在内部设置指针。自C ++ 17开始提供std::is_aggregate后,可以根据()是否为聚合类型自动选择{}T语法来改进解决方案。