P0137引入了函数模板, std::launder
并在有关联合,生命周期和指针的部分中对标准进行了许多更改.
这篇论文解决了什么问题?我必须注意哪些语言的变化?我们在做什么launder
?
我刚读完
坦率地说,我只能挠头。
让我们从@NicolBolas 接受的答案中的第二个例子开始:
Run Code Online (Sandbox Code Playgroud)aligned_storage<sizeof(int), alignof(int)>::type data; new(&data) int; int *p = std::launder(reinterpret_cast<int*>(&data));
[basic.life]/8 告诉我们,如果在旧对象的存储中分配新对象,则无法通过指向旧对象的指针访问新对象。
std::launder
允许我们回避这一点。
那么,为什么不改变语言标准,以便data
通过 a访问reinterpret_cast<int*>(&data)
是有效/适当的?在现实生活中,洗钱是一种向法律隐瞒现实的方式。但我们没有什么可隐瞒的——我们在这里做的事情完全合法。那么当编译器std::launder()
注意到我们正在以data
这种方式访问时,为什么不能将其行为更改为它的行为呢?
继续第一个例子:
Run Code Online (Sandbox Code Playgroud)X *p = new (&u.x) X {2};
因为 X 是微不足道的,我们不需要在创建一个新对象之前销毁旧对象,所以这是完全合法的代码。新对象的 n 成员将为 2。
所以告诉我……什么会
u.x.n
回来?显而易见的答案是 2。但这是错误的,因为允许编译器假设一个真正的
const
变量(不仅仅是一个 const&,而是一个声明的对象变量const
)永远不会改变。但我们只是改变了它。
那么,为什么不使编译器不能被允许作一个假设,当我们写这样的代码,通过该指针访问恒场?
为什么使用这个伪函数来在形式语言语义中打一个洞是合理的,而不是根据代码是否像这些示例中那样将语义设置为它们需要的样子?
该std::launder
函数要求通过结果可以访问的每个字节都可以通过参数访问。“可达到”的定义如下:
通过一个指向对象Y的指针值可以到达一个存储字节,如果该对象位于Y所占的存储空间之内,则该对象可以与Y进行指针可互转换;如果Y是一个数组元素,则该数组将立即包含在数组对象中。
根据对另一个问题的回答,此限制“ ...意味着您将无法launder
获得一个指针,该指针将允许您访问比源指针值允许更多的字节,这将带来不确定的行为。”
这对于TC给出的示例是有意义的,但是在原始对象已被新对象替换的情况下,我不理解如何解释它,这是预期的原始目的std::launder
。该标准具有以下示例:
struct X { const int n; };
X *p = new X{3};
const int a = p->n;
new (p) X{5}; // p does not point to new object (6.8) because X::n is const
const int b = p->n; // undefined behavior
const int c = std::launder(p)->n; // OK
Run Code Online (Sandbox Code Playgroud)
在这种情况下,到时间std::launder
被调用时,由p
- 指向的对象--原始X
对象--已经不存在,因为在其所占用的存储中创建新对象已隐式结束了其生命周期([基本。生活] /1.4)。因此,似乎有通过任何字节可达p
因为 …
它就像std::optional
,但不存储额外的布尔.用户必须确保仅在初始化后访问.
template<class T>
union FakeOptional { //Could be a normal struct in which case will need std::aligned storage object.
FakeOptional(){} //Does not construct T
template<class... Args>
void emplace(Args&&... args){
new(&t) T{std::forward<Args&&>(args)...};
}
void reset(){
t.~T();
}
operator bool() const {
return true;
}
constexpr const T* operator->() const {
return std::launder(&t);
}
constexpr T* operator->() {
return std::launder(&t);
}
T t;
};
Run Code Online (Sandbox Code Playgroud)
如果您想知道为什么我需要这样一个模糊的数据结构,请点击此处:https://gitlab.com/balki/linkedlist/tree/master
题
std::launder
吗?我猜不会.std::launder
仅在c ++ 17中可用,如何在c ++ 14中实现上面的类?boost::optional …
这是来自 C++20 规范 ( [basic.life]/8 )的代码示例:
struct C {
int i;
void f();
const C& operator=( const C& );
};
const C& C::operator=( const C& other) {
if ( this != &other ) {
this->~C(); // lifetime of *this ends
new (this) C(other); // new object of type C created
f(); // well-defined
}
return *this;
}
int main() {
C c1;
C c2;
c1 = c2; // well-defined
c1.f(); // well-defined; c1 refers to a new object of type …
Run Code Online (Sandbox Code Playgroud) 以下代码示例来自 cppreference 上std::launder
:
alignas(Y) std::byte s[sizeof(Y)];
Y* q = new(&s) Y{2};
const int f = reinterpret_cast<Y*>(&s)->z; // Class member access is undefined behavior
Run Code Online (Sandbox Code Playgroud)
在我看来,由于标准中的[basic.life]/6,第三行将导致未定义的行为:
在对象的生命周期开始之前,但在分配对象将占用的存储空间之后...如果...指针用于访问非静态数据成员或调用非静态,则程序具有未定义的行为对象的成员函数。
为什么placement new没有开始object的生命周期Y
?
std::launder
故意混淆抽象机/编译器的指针的来源,以便源和结果可能具有不同的生命周期和类型。当用于例如(静态)向量情况时,您有一个半大的存储空间来保存多个对象,清洗指向切片的“干”指针会产生适当类型的干净但“湿”的指针(湿的,如清洗过的) :
// appropriately sized and aligned data member
std::byte* const dry = data + some_index;
T* const wet = std::launder(reinterpret_cast<T*>(dry));
Run Code Online (Sandbox Code Playgroud)
这个想法是编译器无法“看到”过去的std::launder
. 即使没有实现位于单独的编译单元中。问题是这是否仍然会浪费合理的优化机会。
例如:
T object;
// ...
T* ptr = &object;
ptr->member = 42;
return ptr->member;
Run Code Online (Sandbox Code Playgroud)
据推测,在读取其内容时,不需要单独检索object
和的地址,因为它可能仍然位于某个寄存器中。member
但是,如果对访问的访问ptr
总是被洗白,例如,如果它是从operator[](size_type)
必须洗白存储指针的某些地方检索的,则这可能不再成立:
storage[i].member = 42;
return storage[i].member;
// storage[]() launders
Run Code Online (Sandbox Code Playgroud)
storage[i]
?std::launder
一个除了神奇地使 UB 代码变得非 UB 之外什么都不做的函数?含义:如果这是正确的,则意味着类向量结构(即当您需要最高性能时使用的结构)将因引入std::launder
. std::vector<T>
就会落后于简单的T*
。
[1]
\n\n是否有任何情况不需要将p0593r6添加到 C++20 ( \xc2\xa7 6.7.2.11对象模型[intro.object] ) std::launder
,而需要 C++17 中的相同用例std::launder
,或者它们是完全正交?
[2]
\n\n[ptr::launder]规范中的示例是:
\n\nstruct X { int n; };\nconst X *p = new const X{3};\nconst int a = p->n;\nnew (const_cast<X*>(p)) const X{5}; // p does not point to new object ([basic.life]) because its type is const\nconst int b = p->n; // undefined behavior\nconst int c = std::launder(p)->n; // OK\n
Run Code Online (Sandbox Code Playgroud)\n\n@Nicol Bolas在这个 SO 答案中给出了另一个例子,使用指向有效存储但类型不同的指针:
\n\n …其中的先决条件上的std::launder
要求,对象是其生命周期内。我认为这是能够取消引用元素的必要条件。这是否意味着,如果我获得指向数组元素的指针,那么如果我只执行算术运算,则不需要清洗?还是只是UB?
一个清晰的代码示例。在对代码的注释中做了一些澄清问题的注释。
alignas(T) std::byte memory[3 * sizeof(T)]; // implicitly creates an array of 3 T
// elements on the stack
auto arr_ptr = std::launder(reinterpret_cast<T(*)[3]>(memory));
// Now we have an implictly created array of 3 T, but unless T is an
// implicit-lifetime type, no element has began its lifetime yet
auto ptr1 = reinterpet_cast<T*>(memory); // points to the storage occupied by the
// first element of T
// is arithmetic on ptr1 valid? Or …
Run Code Online (Sandbox Code Playgroud) 我想编写自己的“小向量”类型,第一个障碍是弄清楚如何实现堆栈存储。
我偶然发现了std::aligned_storage
,它似乎是专门为实现任意堆栈存储而设计的,但我很不清楚什么是安全的,什么是不安全的。cppreference.com方便地提供了一个使用 的示例std::aligned_storage
,我将在这里重复一下:
template<class T, std::size_t N>
class static_vector
{
// properly aligned uninitialized storage for N T's
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
std::size_t m_size = 0;
public:
// Create an object in aligned storage
template<typename ...Args> void emplace_back(Args&&... args)
{
if( m_size >= N ) // possible error handling
throw std::bad_alloc{};
// construct value in memory of aligned storage
// using inplace operator new
new(&data[m_size]) T(std::forward<Args>(args)...);
++m_size;
}
// Access an …
Run Code Online (Sandbox Code Playgroud) c++ ×10
stdlaunder ×10
c++17 ×6
c++20 ×2
memory ×2
arrays ×1
c++-faq ×1
c++14 ×1
machine-code ×1
optimization ×1
rationale ×1