C++ 澄清P0137的细节

C++ 澄清P0137的细节,c++,language-lawyer,c++17,C++,Language Lawyer,C++17,在下面的代码中,我对标准中关于对象寿命的词语(加上的措辞)进行了细致的描述 请注意,根据P0137,所有内存分配都是通过unsigned char类型的适当对齐存储进行的 还要注意,Foo是一个POD,具有一个简单的构造函数 问题: A.如果我误解了标准,并且这里有任何UB,请指出(或者确认没有UB) B.鉴于结构微不足道,且不进行实际初始化,A、B、C、D、E、F的初始化是否严格必要。如果是,请说明本标准的哪一部分在这方面与[对象.寿命]相矛盾或澄清 代码: #包括 //具有平凡构造函数的PO

在下面的代码中,我对标准中关于对象寿命的词语(加上的措辞)进行了细致的描述

请注意,根据P0137,所有内存分配都是通过unsigned char类型的适当对齐存储进行的

还要注意,
Foo
是一个POD,具有一个简单的构造函数

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

B.鉴于结构微不足道,且不进行实际初始化,A、B、C、D、E、F的初始化是否严格必要。如果是,请说明本标准的哪一部分在这方面与[对象.寿命]相矛盾或澄清

代码:
#包括
//具有平凡构造函数的POD
结构Foo
{
int x;
};
结构销毁1
{
void运算符()(Foo*p)
{
//RAII保证正确的销毁顺序
自动记忆=std::unique_ptr(reinterpret_cast(p));
p->~Foo();//A
}
};
std::unique_ptr create1()
{
//RAII保证正确的异常处理
auto p=std::make_unique(sizeof(Foo));
auto pCandidate=重新解释(p.get());
新建(pCandidate)Foo();//B
return std::unique_ptr(reinterpret_cast(p.release()),
(1());
}
结构调用自由
{
void运算符()(void*p)常量{std::free(p);}
};
使用malloc_ptr=std::unique_ptr;
结构销毁2
{
void运算符()(Foo*pfoo)常量{
//RAII保证正确的销毁顺序
自动记忆=malloc_ptr(重新解释铸造(pfoo));
pfoo->~Foo();//C
}
};
std::unique_ptr create2()
{
//RAII保证正确的异常处理
自动p=malloc_ptr(重新解释演员阵容(std::malloc(sizeof(Foo)));
auto pCandidate=重新解释(p.get());
新建(pCandidate)Foo();//D
return std::unique_ptr(reinterpret_cast(p.release()),
2());
}
结构节点{
void运算符()(Foo*p){
p->~Foo();/E
}
};
std::共享_ptr提供()
{
alignas(Foo)静态无符号字符存储[sizeof(Foo)];
自动生成=[]{
自动p=重新解释(存储);
new(p)Foo();//F
返回std::shared_ptr(p,nodelete());
};
静态std::shared_ptr pCandidate=make();
返回日期;
}
int main()
{
auto foo1=create1();
auto foo2=create2();
auto foo3=provide();
foo1->x=1;
foo2->x=2;
foo3->x=3;
}
On
create1
那个指针总是正确的

使用“新位置”不是可选的。[intro.object]/1告诉我们:

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

当您分配一个
无符号字符[]
时,它就是您在该存储中创建的对象。你不能简单地假装它是一个
Foo
,仅仅因为
Foo
是一个聚合。[intro.object]/1不允许这样做。必须通过上面列出的机制之一显式创建该对象。由于不能使用定义、
union
成员激活或具有任意内存缓冲区的临时对象从现有存储创建对象,因此创建对象的唯一途径是使用新表达式

具体来说,安置是新的

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

auto memory = std::unique_ptr<unsigned char[]>(reinterpret_cast<unsigned char*>(p));
p->~Foo();
与新表达式不同,
malloc
从不通过[intro.object]/1创建对象;它只获取存储空间。因此,再次需要新的安置

类似地,
free
只是释放内存;它不处理对象。因此,您的
delete2
基本上是很好的(尽管
malloc_ptr
在那里的使用使它变得不必要的混乱)

上提供
这与您的其他示例具有相同的[basic.life]/8问题:

alignas(Foo) static unsigned char storage[sizeof(Foo)];
static auto pCandidate = std::shared_ptr<Foo>(new(storage) Foo(), nodelete());
return pCandidate;
alignas(Foo)静态无符号字符存储[sizeof(Foo)];
static auto pCandidate=std::shared_ptr(新的(存储)Foo(),nodelete());
返回日期;
但除此之外,它是好的(只要你不打破它在其他地方)。为什么?这很复杂

[basic.start.term]/1告诉我们静态对象的销毁顺序与其初始化顺序相反。[stmt.decl]/4告诉我们,块作用域静态对象是按照它们在函数中遇到的顺序初始化的

因此,我们知道
pCandidate
将在
存储之前销毁。只要您没有在静态变量中保留该
共享的\u ptr
的副本,或者在终止之前未能销毁/重置所有此类共享对象,您就可以了


总之,使用
无符号字符块实际上是C++11之前的版本。我们有现在。使用它们。

“核心问题1776:包含引用成员的类对象的替换”基于一个明显而严重的解释错误,因此应该被忽略。错误在这里:

起草说明:这保持了现状,即malloc本身并非如此 足以创建一个对象

这与“仅malloc一个”确实足以创建对象的现状背道而驰,因为malloc返回适当对齐的存储,这已经并且始终足以创建对象


核心问题不是标准。这是对标准的一种看法。这种观点是错误的。

如果您认真对待1776年的核心问题,并且从未投票赞成“仅malloc不足以创建对象”的想法,那么您必须认真对待这些想法:

  • 在C++中,没有UB的联盟直到最近才被使用,因为它们的成员的生命周期没有定义
  • 弦升
    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;
    
    auto memory = std::unique_ptr<unsigned char[]>(reinterpret_cast<unsigned char*>(p));
    p->~Foo();
    
    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;
    
    alignas(Foo) static unsigned char storage[sizeof(Foo)];
    static auto pCandidate = std::shared_ptr<Foo>(new(storage) Foo(), nodelete());
    return pCandidate;