正在构造格式正确的 char 数组中的对象
Is constructing objects in a char array well-formed
这几乎是标准教科书对 placement new 的使用
template<size_t Len, size_t Align>
class aligned_memory
{
public:
aligned_memory() : data((char*)(((std::uintptr_t)mem + Align - 1) & -Align)) {}
char* get() const {return data;}
private:
char mem[Len + Align - 1];
char* data;
};
template<typename T, size_t N>
class Array
{
public:
Array() : sz(0) {}
void push_back(const T& t)
{
new (data.get() + sz++ * sizeof(T)) T(t);
}
void pop_back()
{
((T*)data.get() + --sz)->~T();
}
private:
aligned_memory<N * sizeof(T), alignof(T)> data;
size_t sz;
};
看起来很好,直到我们研究严格别名,这是否格式正确似乎存在一些冲突
阵营不合格
- Strict aliasing rule and 'char *' pointers
阵营井井有条
- Does encapsulated char array used as object breaks strict aliasing rule
- How to avoid strict aliasing errors when using aligned_storage
他们都同意 char*
可以总是引用另一个对象,但有些人指出反过来这样做是不正确的。
很明显,我们的 char[]
转换为 char*
,然后转换为 T*
,用于调用其析构函数。
那么,上面的程序是否违反了严格的别名规则?具体来说,它在标准中的什么地方说它是格式正确的还是格式错误的?
编辑:作为背景信息,这是为 C++0x 编写的,在 alignas
和 std::launder
出现之前。不是特别要求 C++0x 解决方案,但它是首选。
alignof
是作弊,但这里仅供参考。
从无数有用的评论中收集提示,这是我对正在发生的事情的解释。
TLDR 格式正确‡见编辑
按照我认为更符合逻辑的顺序引用 [basic.life]†
The properties ascribed to objects and references throughout this International Standard apply for a given object or reference only during its lifetime.
An object is said to have non-vacuous initialization if it is of a class or aggregate type and it or one of its subobjects is initialized by a constructor other than 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
if the object has non-vacuous initialization, its initialization is complete.
The lifetime of an object o of type T
ends when:
if T is a class type with a non-trivial destructor , the destructor call starts, or
the storage which the object occupies is released, or is reused by an object that is not nested within o
来自[basic.lval]†
If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined
the dynamic type of the object,
a cv-qualified version of the dynamic type of the object,
a type similar to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
a char
, unsigned char
, or std::byte
type.
我们推断
当另一个对象重用 space.
时,char[]
中的 char
的生命周期结束
T
类型对象的生命周期从 push_back
被调用时开始。
由于地址 ((T*)data.get() + --sz)
始终是类型为 T
的对象的地址,其生命周期已经开始但尚未结束,因此调用 ~T()
是有效的有了它。
在此过程中,aligned_memory
中的 char[]
和 char*
为 T
类型的对象设置了别名,但这样做是合法的。此外,没有从它们获得 glvalue,因此它们可能是任何类型的指针。
在评论中回答我自己的问题,使用 any 内存作为存储是否也是格式正确的
U u;
u->~U();
new (&u) T;
((T*)&u)->~T();
new (&u) U;
根据以上 4 点,答案是 是‡见编辑,如只要 U
的对齐不弱于 T
.
‡ 编辑:我忽略了 [basic.life]
的另一段
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
the storage for the new object exactly overlays the storage location which the original object occupied, and
the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
the original object was a most derived object of type T
and the new object is a most derived object of type T
(that is, they are not base class subobjects).
这意味着即使使用对象是合式的,但获取对象的方式却不是。具体来说,post C++17,std::launder
必须调用
(std::launder((T*)data.get()) + --sz)->~T();
在 C++17 之前,解决方法是使用从 new 位置获取的指针来代替
T* p = new (data.get() + sz++ * sizeof(T)) T(t); // store p somewhere
† 引自 n4659,据我所知,同样适用于 n1905
Placement-new 在指定位置创建一个对象 (C++14 expr.new/1),并结束占据该位置的任何其他对象的生命周期 (basic.life/1 .4).
代码 ((T*)data.get() + --sz)->~T();
在存在类型 T
的对象的位置访问类型 T
的对象。这可以。如果该位置曾经有一个char数组是无关紧要的。
这几乎是标准教科书对 placement new 的使用
template<size_t Len, size_t Align>
class aligned_memory
{
public:
aligned_memory() : data((char*)(((std::uintptr_t)mem + Align - 1) & -Align)) {}
char* get() const {return data;}
private:
char mem[Len + Align - 1];
char* data;
};
template<typename T, size_t N>
class Array
{
public:
Array() : sz(0) {}
void push_back(const T& t)
{
new (data.get() + sz++ * sizeof(T)) T(t);
}
void pop_back()
{
((T*)data.get() + --sz)->~T();
}
private:
aligned_memory<N * sizeof(T), alignof(T)> data;
size_t sz;
};
看起来很好,直到我们研究严格别名,这是否格式正确似乎存在一些冲突
阵营不合格
- Strict aliasing rule and 'char *' pointers
阵营井井有条
- Does encapsulated char array used as object breaks strict aliasing rule
- How to avoid strict aliasing errors when using aligned_storage
他们都同意 char*
可以总是引用另一个对象,但有些人指出反过来这样做是不正确的。
很明显,我们的 char[]
转换为 char*
,然后转换为 T*
,用于调用其析构函数。
那么,上面的程序是否违反了严格的别名规则?具体来说,它在标准中的什么地方说它是格式正确的还是格式错误的?
编辑:作为背景信息,这是为 C++0x 编写的,在 alignas
和 std::launder
出现之前。不是特别要求 C++0x 解决方案,但它是首选。
alignof
是作弊,但这里仅供参考。
从无数有用的评论中收集提示,这是我对正在发生的事情的解释。
TLDR 格式正确‡见编辑
按照我认为更符合逻辑的顺序引用 [basic.life]†
The properties ascribed to objects and references throughout this International Standard apply for a given object or reference only during its lifetime.
An object is said to have non-vacuous initialization if it is of a class or aggregate type and it or one of its subobjects is initialized by a constructor other than 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, andif the object has non-vacuous initialization, its initialization is complete.
The lifetime of an object o of type
T
ends when:
if T is a class type with a non-trivial destructor , the destructor call starts, or
the storage which the object occupies is released, or is reused by an object that is not nested within
o
来自[basic.lval]†
If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined
the dynamic type of the object,
a cv-qualified version of the dynamic type of the object,
a type similar to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
a
char
,unsigned char
, orstd::byte
type.
我们推断
当另一个对象重用 space.
时,char[]
中的char
的生命周期结束T
类型对象的生命周期从push_back
被调用时开始。由于地址
((T*)data.get() + --sz)
始终是类型为T
的对象的地址,其生命周期已经开始但尚未结束,因此调用~T()
是有效的有了它。在此过程中,
aligned_memory
中的char[]
和char*
为T
类型的对象设置了别名,但这样做是合法的。此外,没有从它们获得 glvalue,因此它们可能是任何类型的指针。
在评论中回答我自己的问题,使用 any 内存作为存储是否也是格式正确的
U u;
u->~U();
new (&u) T;
((T*)&u)->~T();
new (&u) U;
根据以上 4 点,答案是 是‡见编辑,如只要 U
的对齐不弱于 T
.
‡ 编辑:我忽略了 [basic.life]
的另一段If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
the storage for the new object exactly overlays the storage location which the original object occupied, and
the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
the original object was a most derived object of type
T
and the new object is a most derived object of typeT
(that is, they are not base class subobjects).
这意味着即使使用对象是合式的,但获取对象的方式却不是。具体来说,post C++17,std::launder
必须调用
(std::launder((T*)data.get()) + --sz)->~T();
在 C++17 之前,解决方法是使用从 new 位置获取的指针来代替
T* p = new (data.get() + sz++ * sizeof(T)) T(t); // store p somewhere
† 引自 n4659,据我所知,同样适用于 n1905
Placement-new 在指定位置创建一个对象 (C++14 expr.new/1),并结束占据该位置的任何其他对象的生命周期 (basic.life/1 .4).
代码 ((T*)data.get() + --sz)->~T();
在存在类型 T
的对象的位置访问类型 T
的对象。这可以。如果该位置曾经有一个char数组是无关紧要的。