new A[0]:为具有不可访问构造函数的类初始化具有动态存储持续时间的零大小数组

Fed*_*dor 13 c++ constructor new-operator language-lawyer

我有一个 C++ 概念来检查对象数组是否可以动态分配:

template< class T, int N >
concept heap_constructible = requires() {
    delete[] new T[N];
};
Run Code Online (Sandbox Code Playgroud)

我意外地发现,在零大小数组N=0和不可访问的(例如private:)构造函数的情况下,编译器的评估会出现分歧:

class A {
    A();
};

static_assert( !heap_constructible<A, 5> ); // OK everywhere
static_assert( heap_constructible<A, 0> );  // OK in GCC only
Run Code Online (Sandbox Code Playgroud)

只有 GCC 似乎允许零大小分配A-objects。Clang 打印错误:

calling a private constructor of class 'A'
Run Code Online (Sandbox Code Playgroud)

如果概念稍作修改:

template< class T, int N >
concept heap_constructible1 = requires() {
    delete[] new T[N]{}; // note additional empty braced list
};
Run Code Online (Sandbox Code Playgroud)

Clang 也接受它:

static_assert( heap_constructible1<A, 0> ); // OK in GCC and Clang
Run Code Online (Sandbox Code Playgroud)

但不是 MSVC,它同时评估heap_constructible<A, 0>和。在线演示:https://gcc.godbolt.org/z/nYr88avM4heap_constructible1<A, 0>false

这里是哪个编译器?

duc*_*uck 4

目前尚未明确new 表达式对构造函数的使用;这是CWG2102(源自Clang 问题)。


new 表达式中绑定的数组不必是常量表达式,如果不是常量表达式,则在运行时之前无法判断new 初始化程序是否覆盖了数组的每个元素,或者是否需要进行额外的初始化对于尾随元素。这意味着,一般来说,元素类型需要是可默认构造的。

然而,当数组界限常量表达式时,这个要求就显得多余了。事实上,所有主要实现都接受#1如下例所示的代码(同时拒绝#2):

struct S {
    S() = delete;
    S(int);
};

const int a = 3;
S* p = new S[a] {1, 2, 3}; // #1

int b = 3;
S* q = new S[b] {1, 2, 3}; // #2
Run Code Online (Sandbox Code Playgroud)

但该标准并没有区分这两种情况。


考虑到这一点,我想说 GCC 的行为在这里是最一致的:既不是默认值([dcl.init.general]/7.2),也不是聚合([dcl.init.aggr])零大小数组的初始化使用T任何 的T构造函数,因此如果new S[3] {1, 2, 3}在上面的示例中没问题,new S[0]也应该没问题。