放置new的返回值

33
考虑以下C++14代码:
#include <cassert>
#include <new>
#include <type_traits>

struct NonStandardLayout
{
    // ...
};

int main()
{
    using T = NonStandardLayout;

    std::aligned_storage_t< sizeof(T), alignof(T) > storage;
    T *const valid_ptr = new(static_cast<void *>(&storage)) T;

    T *const maybe_ptr = reinterpret_cast<T *>(&storage);
    assert(maybe_ptr == valid_ptr); // ???

    valid_ptr->T::~T();
    return 0;
}

这个例子中的assert语句对于任何类型T都不会失败,是否有标准保证?
讨论
查看最新的标准(http://eel.is/c++draft/),我没有找到对这种特定情况的参考,但我发现以下段落可能表明答案是“是”。
我是否正确地认为[expr.new/15]和[new.delete.placement/2]一起声明valid_ptr的值始终等于storage的地址?
如果是这样,reinterpret_cast是否会产生指向完全构造的对象的指针?因为[expr.reinterpret.cast/7]、[expr.static.cast/13]和[basic.compound/4]似乎都表明应该是这样的。
根据我的观察,库实现的默认分配器似乎会进行类似的转换而不必担心!像这样强制转换真的安全吗?
我们怎样才能确定这两个指针是否相同,或者我们能吗?

4
挺好的第一个问题!你已经潜伏足够久了,知道该怎么问了。 - Jean-François Fabre
也许与此无关,但我在某个地方读到过系统的 malloc 保证返回任何数据类型对齐的内存。 - Jean-François Fabre
虽然没有正式保证,但通过推理不同指针值,您可以推断它在PC上永远不会失败。就标准而言,在这里reinterpret_cast的结果可以是任何指针值,除了空指针,因为它没有被使用。除了与空指针比较之外,唯一有保证的用法是将其强制转换回来。 - Cheers and hth. - Alf
答案是 std::aligned_storage_t 的定义。如果它保证了这个属性,那么是的。这与放置 new 如何工作无关。 - Kirill Kobelev
2
这个例子基本上是std::launder的动机之一(另一半是Core 1776)。 - T.C.
显示剩余2条评论
2个回答

7

我很惊讶还没有人提到这个便宜的反例:

struct foo {
  static foo f;
  // might seem dubious but doesn't violate anything
  void* operator new(size_t, void* p) {return &f;}
};

这里有Coliru上的演示

除非调用了特定于类的放置版本,否则您的断言应该成立。如其他答案所解释的那样(主要是标准的非分配operator new仅返回指针参数,而new表达式不会做任何花哨的事情),两个表达式都必须表示相同的地址,并且这些地址都不是某个对象的结束指针,因此根据[expr.eq]/2,它们相等比较。


@MaximEgorushkin,您确定这适用于类特定的覆盖,即上面示例中的静态成员吗?似乎这段话是针对全局版本的? - Columbo
我再仔细考虑了一下,标准明确允许特定于类的就地 new 运算符。因此,我收回之前的评论。 - Maxim Egorushkin
@MaximEgorushkin 首先,你的段落编号是错误的(18.6.2.3),其次,请使用文本形式,比如[new.delete.placement]。数字会不时地发生变化,最新的草案可能会受到ISO引起的大量条款编号更改的影响(第18条现在是第21条)。您可以在eel.is/c++draft上看到这一点。 - Columbo
1
@BoundaryImposition 我不明白,我已经回答了这个问题。我的代码是有效的,并且会破坏他的断言。Sam引用的规范不适用于用户定义的操作符news(如果您认为它适用,请提供引文)。 - Columbo
@godpusti Sam的帖子中的评论是关于new[]表达式,即数组形式。非数组形式不会做任何花哨的事情,因为没有什么花哨的东西需要处理。 - Columbo
显示剩余3条评论

3
18.6.1.3 放置形式 [new.delete.placement] void* operator new(std::size_t size, void* ptr) noexcept; 返回: ptr.
该放置new运算符明确指定会返回传递给它的任何指针。"返回: ptr"。没有比这更清晰的了。
就"来自放置new的返回值"而言,这基本上对我产生了影响: 放置new不会改变其放置的指针,并且它总是返回相同的指针。
你问题中的其他部分都与可能发生的其它类型转换相关;但是,你特别询问了放置new的返回值,因此我认为你接受所有其他转换仅为类型转换,并不会对实际指针产生影响,并且只是在询问放置new -- 但是也可以通过其他转换,并做出类似的决定。

1
问题在于,operator new的数组形式也会返回ptr [new.delete.placement/5],但我们都知道new(ptr) int[10]由于[expr.new/15.4]不会返回ptr。那么非数组版本又是做什么的呢? - godpusti
8
定位分配函数和new表达式是有区别的。 - T.C.
new-expression 调用放置分配函数。请参见 5.3.4(13)和(14)。 “new-placement 语法用于向分配函数提供附加参数... [例如:
  • new T 导致调用 operator new(sizeof(T)),
  • new(2,f) T 导致调用 operator new(sizeof(T),2,f)]” 因此:new(ptr) T 调用 operator new(sizeof(T), ptr)。证毕。
- Sam Varshavchik
SamVarshavchik:考虑@godpusti的数组形式评论,以及需要销毁的项目的事实,数组长度需要存储在某个地方,以便可以销毁每个项目。此外,请考虑T.C.几乎总是正确的。 - Cheers and hth. - Alf
1
该问题未涉及数组形式。对于问题中给出的用例,我认为这看起来很简单明了。至于所有的噪音:我记得很久以前,当我也经常认为自己总是正确时。我很高兴地报告,这是很久以前的事了,从那时起,我简单地成熟了。如果有令人信服的论据或替代答案,可以根据其他证据得出不同的结论,或者发现我的引用中存在缺陷;如果是这样,我会删除它。但是,就目前而言,我相信我的答案是正确的。 - Sam Varshavchik
从技术上讲,该函数在返回指针之前可能会修改ptr。但我同意这不是标准的意思:D - Lightness Races in Orbit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接