初始化对象时丢弃放置新返回值是否可以 [英] Is it OK to discard placement new return value when initializing objects

查看:18
本文介绍了初始化对象时丢弃放置新返回值是否可以的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题来自 this 线程的评论部分,并且在那里也得到了答案.但是,我认为只留在评论部分太重要了.所以我为它做了这个问答.

This question originates from the comment section in this thread, and has also got an answer there. However, I think it is too important to be left in the comment section only. So I made this Q&A for it.

Placement new 可用于在分配的存储中初始化对象,例如,

Placement new can be used to initialize objects at allocated storage, e.g.,

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,它们将作为附加参数传递给分配函数.这种分配函数被称为placement new",在标准分配函数 void* operator new(std::size_t, void*) 之后,简单地返回其第二个参数不变.这用于在分配的存储中构造对象 [...]

If placement_params are provided, they are passed to the allocation function as additional arguments. Such allocation functions are known as "placement new", after the standard allocation function void* operator new(std::size_t, void*), which simply returns its second argument unchanged. This is used to construct objects in allocated storage [...]

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

That means new(p) vec_t{1, 2, 3} simply returns p, and p = new(p) vec_t{1, 2, 3} looks redundant. Is it really OK to ignore the return value?

推荐答案

不管是学究式还是实际操作,忽略返回值都是不行的.

Ignoring the return value is not OK both pedantically and practically.

从迂腐的角度来看

对于 p = new(p) T{...}p 限定为指向由 new 表达式创建的对象的指针,它不包含对于 new(p) T{...},尽管值相同.在后一种情况下,它只能作为指向已分配存储的指针.

For p = new(p) T{...}, p qualifies as a pointer to an object created by a new-expression, which does not hold for new(p) T{...}, despite the fact that the value is the same. In the latter case, it only qualifies as pointer to an allocated storage.

非分配全局分配函数返回它的参数,没有暗示任何副作用,但是一个 new 表达式(无论是否放置)总是返回一个指向它创建的对象的指针,即使它碰巧使用了那个分配函数.

The non-allocating global allocation function returns its argument with no side effect implied, but a new-expression (placement or not) always returns a pointer to the object it creates, even if it happens to use that allocation function.

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

Per cppref's description about the delete-expression (emphasis mine):

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

For the first (non-array) form, expression must be a pointer to a object type or a class type contextually implicitly convertible to such pointer, and its value must be either null or pointer to a non-array object created by a new-expression, or a pointer to a base subobject of a non-array object created by a new-expression. If expression is anything else, including if it is a pointer obtained by the array form of new-expression, the behavior is undefined.

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

Failing to p = new(p) T{...} therefore makes delete p undefined behavior.

从实用的角度来看

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

Technically, without p = new(p) T{...}, p does not point to the newly-initialized T, despite the fact that the value (memory address) is the same. The compiler may therefore assume that p still refers to the T that was there before the placement new. Consider the code

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

即使在 (2) 之后,编译器可能会假设 p 仍然引用在 (1) 处初始化的旧值,并生成从而导致不正确的优化.例如,如果 T 有一个 const 成员,编译器可能会将其值缓存在 (1) 并在 (2) 之后仍然使用它.

Even after (2), the compiler may assume that p still refers to the old value initialized at (1), and make incorrect optimizations thereby. For example, if T had a const member, the compiler might cache its value at (1) and still use it even after (2).

p = new(p) T{...} 有效地禁止了这种假设.另一种方法是使用 std::launder(),但是将placement new的返回值分配回p会更容易和更简洁.

p = new(p) T{...} effectively prohibits this assumption. Another way is to use std::launder(), but it is easier and cleaner to just assign the return value of placement new back to 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)...};
}

这些函数模板总是在内部设置指针.std::is_aggregate 自 C++ 起可用在 17 中,可以通过根据 T 是否为聚合类型自动在 (){} 语法之间进行选择来改进解决方案.

These function templates always set the pointer internally. With std::is_aggregate available since C++17, the solution can be improved by automatically choosing between () and {} syntax based on whether T is an aggregate type.

这篇关于初始化对象时丢弃放置新返回值是否可以的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆