澄清P0137的细节 [英] clarification of specifics of P0137

查看:84
本文介绍了澄清P0137的细节的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在以下代码中,我对以下标准的措辞一丝不苟(加上

In the following code I have been meticulous in the following of the standard's words (plus in the light of the wording of P0137) on object lifetimes.

请注意,按照P0137,所有内存分配都是通过适当对齐的unsigned char类型的存储来完成的.

Note that all memory allocation is through suitably-aligned storage of type unsigned char, as per P0137.

还请注意,Foo是POD,具有琐碎的构造函数.

Note also that Foo is a POD, with a trivial constructor.

A.如果我误解了标准,并且这里有UB,请指出(或确认没有UB)

A. If I have misunderstood the standard, and there is any UB here, please kindly point it out (or alternatively confirm that there is no UB)

B.鉴于构造是微不足道的,并且不执行任何实际的初始化,因此是否有必要严格地在A,B,C,D,E,F处进行初始化.如果是这样,请指出这方面标准的哪个部分与[object.lifetime]相矛盾或明确.

B. Are the initialisations at A, B, C, D, E, F strictly necessary in light of the fact that the construction is trivial, and performs no actual initialisation. If so, please indicate which part of the standard contradicts or clarifies [object.lifetime] in this regard.

#include <memory>

// a POD with trivial constructor
struct Foo 
{
  int x;
};

struct destroy1
{
  void operator()(Foo* p)
  {
    // RAII to guarantee correct destruction order
    auto memory = std::unique_ptr<unsigned char[]>(reinterpret_cast<unsigned char*>(p));
    p->~Foo(); // A
  }
};
std::unique_ptr<Foo, destroy1> create1()
{
  // RAII to guarantee correct exception handling
  auto p = std::make_unique<unsigned char[]>(sizeof(Foo));
  auto pCandidate = reinterpret_cast<Foo*>(p.get());
  new (pCandidate) Foo(); // B
  return std::unique_ptr<Foo, destroy1>(reinterpret_cast<Foo*>(p.release()), 
                                        destroy1());
}

struct call_free
{
  void operator()(void *p) const { std::free(p); } 
};
using malloc_ptr = std::unique_ptr<unsigned char, call_free>;

struct destroy2
{
  void operator()(Foo *pfoo) const {
    // RAII to guarantee correct destruction order
    auto memory = malloc_ptr(reinterpret_cast<unsigned char*>(pfoo));
    pfoo->~Foo(); // C
  }
};

std::unique_ptr<Foo, destroy2> create2()
{
    // RAII to guarantee correct exception handling
  auto p = malloc_ptr(reinterpret_cast<unsigned char*>(std::malloc(sizeof(Foo))));
  auto pCandidate = reinterpret_cast<Foo*>(p.get());
  new (pCandidate) Foo(); // D
  return std::unique_ptr<Foo, destroy2>(reinterpret_cast<Foo*>(p.release()), 
                                        destroy2());
}

struct nodelete {
  void operator()(Foo * p) {
    p->~Foo();  // E
  }
};

std::shared_ptr<Foo> provide()
{
  alignas(Foo) static unsigned char  storage[sizeof(Foo)];

  auto make = [] {
    auto p = reinterpret_cast<Foo*>(storage);
    new (p) Foo (); // F
    return std::shared_ptr<Foo>(p, nodelete());
  };

  static std::shared_ptr<Foo> pCandidate = make();

  return pCandidate;
}


int main()
{
  auto foo1 = create1();
  auto foo2 = create2();
  auto foo3 = provide();

  foo1->x = 1;
  foo2->x = 2;
  foo3->x = 3;
}

推荐答案

create1

std::unique_ptr<Foo, destroy1>(reinterpret_cast<Foo*>(p.release()), destroy1());

这不起作用,因为您使用了错误的指针.

This doesn't work, because you're using the wrong pointer.

p.release()认为它指向unsigned char[].但是,这不是您要指向的对象.您要指向的是位于此数组中的对象,即您创建的Foo.

p.release() thinks it points to an unsigned char[]. However, that's not the object you want to point to. What you want to point to is the object that lives inside this array, the Foo you've created.

因此您现在要服从[basic.life]/8.要点是,如果它们具有相同的类型,则只能将先前的指针用作指向新对象的指针.他们不在你的情况下.

So you are now subject to [basic.life]/8. The gist of that is that you can only use the previous pointer as a pointer to the new object if they are of the same type. Which they're not in your case.

现在,我可以告诉您launder指针,但是处理此问题的更合理的方法是只存储由placement-new调用返回的指针:

Now, I could tell you to launder the pointer, but the more reasonable way to handle this is to just store the pointer returned by the placement-new call:

auto p = std::make_unique<unsigned char[]>(sizeof(Foo));
auto ret = std::unique_ptr<Foo, destroy1>(new(p.get()) Foo(), destroy1());
p.release();
return ret;

该指针将始终是正确的.

That pointer will always be correct.

您对新展示位置的使用不是可选. [intro.object]/1告诉我们:

Your use of of placement-new is not optional. [intro.object]/1 tells us:

在隐式更改联合的活动成员(9.3)或创建临时对象(4.4、12.2)时,将通过定义(3.1),新表达式(5.3.4)创建对象.

An object is created by a definition (3.1), by a new-expression (5.3.4), when implicitly changing the active member of a union (9.3), or when a temporary object is created (4.4, 12.2).

分配unsigned char[]时,这就是您在该存储中创建的对象.您不能仅仅因为Foo是一个聚合就假装它是Foo. [intro.object]/1不允许这样做.您必须通过上面列出的机制之一显式创建该对象.由于您不能使用定义,union成员激活或带有任意内存缓冲区的临时对象来从现有存储中创建对象,因此创建对象的唯一途径就是new-expression.

When you allocate an unsigned char[], that's the object you have created in that storage. You cannot simply pretend that it is a Foo, just because Foo is an aggregate. [intro.object]/1 doesn't allow that. You must explicitly create that object via one of the mechanisms listed above. Since you can't use a definition, union member activation, or temporary objects with arbitrary memory buffers to create objects from existing storage, the only recourse you have to create objects is a new-expression.

具体来说,是新的展示位置.

Specifically, placement-new.

对于delete1,您确实需要一个自定义删除器,因为默认删除器将在Foo指针上调用delete.您的代码如下:

As for delete1, you do need a custom deleter, since the default deleter will call delete on the Foo pointer. Your code is as follows:

auto memory = std::unique_ptr<unsigned char[]>(reinterpret_cast<unsigned char*>(p));
p->~Foo();

unsigned char[]具有一些特殊的逻辑,这要归功于[intro.object]/3-4,当对象在其存储中分配时,它的行为如何.如果对象完全覆盖unsigned char[]的存储,则它的作用就好像对象是在数组内分配的一样.这意味着unsigned char[]在技术上仍然存在;它并没有破坏字节数组.

unsigned char[] has some special logic to it, in terms of how it behaves when objects are allocated in their storage, thanks to [intro.object]/3-4. If the object entirely overlays the storage of the unsigned char[], then it functions as if the object were allocated within the array. That means that the unsigned char[] is still technically there; it has not destroy the byte array.

这样,您仍然可以删除字节数组,就像这里的代码一样.

As such, you can still delete the byte array, which your code here does.

这也是错误的,因为进一步违反了[basic.life]/8.固定版本类似于上面的内容:

This is also wrong, due to further violations of [basic.life]/8. A fixed version would be similar to the above:

auto p = malloc_ptr(reinterpret_cast<unsigned char*>(std::malloc(sizeof(Foo))));
auto ret std::unique_ptr<Foo, destroy2>(new(p.get()) Foo(), destroy2());
p.release();
return ret;

与new-expressions不同,malloc永远不会通过[intro.object]/1创建对象;它只获取存储空间.因此,再次需要重新放置.

Unlike new-expressions, malloc never creates an object via [intro.object]/1; it only acquires storage. As such, placement-new is again required.

类似地,free只是释放内存;它不处理对象.因此,您的delete2本质上很好(尽管使用malloc_ptr会造成不必要的混乱).

Similarly, free just releases memory; it doesn't deal with objects. So your delete2 is essentially fine (though the use of malloc_ptr there makes it needlessly confusing).

与您的其他示例一样,存在[basic.life]/8问题:

This has the same [basic.life]/8 problems that the rest of your examples have:

alignas(Foo) static unsigned char storage[sizeof(Foo)];
static auto pCandidate = std::shared_ptr<Foo>(new(storage) Foo(), nodelete());
return pCandidate;

但是除此之外,还可以(只要您在其他地方不要破坏它).为什么?太复杂了.

But other than that, it's fine (so long as you don't break it elsewhere). Why? That's complex.

[basic.start.term]/1告诉我们,静态对象是按照初始化时的相反顺序销毁的. [stmt.decl]/4告诉我们,块范围内的静态对象是按照它们在函数中遇到的顺序进行初始化的.

[basic.start.term]/1 tells us that static objects are destroyed in the reverse order of their initialization. And [stmt.decl]/4 tells us that block-scoped static objects are initialized in the order they are encountered in a function.

因此,我们知道pCandidate将在之前 storage被销毁.只要您没有在静态变量中保留该shared_ptr的副本,否则在终止之前无法销毁/重置所有此类共享对象,就可以了.

Therefore, we know that pCandidate will be destroyed before storage. So long as you don't keep a copy of that shared_ptr in a static variable, or otherwise fail to destroy/reset all such shared objects before termination, you should be fine.

所有所说的,使用unsigned char块实际上是C ++ 11之前的版本.我们有 std::aligned_storage

That all being said, using blocks of unsigned char is really pre-C++11. We have std::aligned_storage and std::aligned_union now. Use them.

这篇关于澄清P0137的细节的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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