【问题标题】:Default constructor of T is bypassed when {} are used with new to create std::array<T, n>当 {} 与 new 一起使用来创建 std::array<T, n> 时,会绕过 T 的默认构造函数
【发布时间】:2020-05-25 15:55:56
【问题描述】:

让我们考虑这个类:

class A {
public:
    A() = delete;

    A(int i) :
            i_m(i) {
        std::cout << __PRETTY_FUNCTION__ << ' ' << i_m << '\n';
    }

    ~A() {
        std::cout << __PRETTY_FUNCTION__ << ' ' << i_m << '\n';
    }

private:
    int i_m{1234567890};
};

默认构造函数被显式删除,因此 AFAIK A 只能从整数构造。数据成员i_m的默认初始化永远不会被使用。

让我们考虑一下这个程序:

int main() {
    using T = std::array<A, 2>;

    //T a;
    // error: use of deleted function 'std::array<A, 2>::array()'
    // note: 'std::array<A, 2>::array()' is implicitly deleted because the default definition would be ill-formed
    // error: use of deleted function 'A::A()'

    //T b{};
    // error: use of deleted function 'A::A()'
}

再一次,这对我来说似乎完全没问题。

现在让我们考虑另一个程序:

int main() {
    using T = std::array<A, 2>;
    auto foo = new T{};
    delete foo;

    auto foo_init = new T{1, 2};
    delete foo_init;

//  auto zorg = new T();
//  delete zorg;
//  error: use of deleted function 'std::array<A, 2>::array()'
//  note: 'std::array<A, 2>::array()' is implicitly deleted because the default definition would be ill-formed:
//  error: use of deleted function 'A::A()'

    auto zorg_init = new T({3, 4});
    delete zorg_init;
}

此代码确实编译(没有警告)并生成以下输出:

A::~A() 0
A::~A() 38870160
A::A(int) 1
A::A(int) 2
A::~A() 2
A::~A() 1
A::A(int) 3
A::A(int) 4
A::~A() 4
A::~A() 3

现在我觉得有些东西很好。 auto foo = new T{}; 这行怎么可能不被认为格式错误?类A的初始化在这里完全绕过了。

这段代码不编译是不值得的:

int main() {
    auto p = new A{};
    delete p;
}

预期的错误:

error: use of deleted function 'A::A()'

我使用以下选项测试了这些代码:-Wall -Wextra -pedantic -std=c++17gcc -v 在我的电脑上给出:gcc version 7.2.0 (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project)

任何解释将不胜感激:)


PS:在 Compiler Explorer 中进行详尽的测试后(使用示例 https://godbolt.org/z/5VZLU_),这似乎是 gcc 7.x 中的一个问题。事实上,只有 7.x 版本的 gcc 编译了这个示例。 gcc 6.4 没有。 gcc 8.1 也没有。也不叮当。 msvc 都没有。

你能确认这里没有UB,这真的是一个编译器错误吗?

【问题讨论】:

标签: c++ arrays constructor compiler-errors new-operator


【解决方案1】:

T b{}; 无法编译而 new T{} 的事实表明这是一个编译器错误。但是,标准本身可能存在缺陷。原因如下。


这个问题的技术答案是:代码是格式良好的,因为标准明确说明了这一点。 [array.overview]/2

array 是一个聚合,最多可以使用 N 进行列表初始化 类型可转换为T的元素。

初始化器{}确实包含“最多N个元素,其类型可转换为T”——它包含0个元素,all of which可转换为A


那么元素是如何初始化的呢?这个标准不是很清楚。 [array.overview]/3 是这样说的:

array 满足容器和容器的所有要求 可逆容器([container.requirements]),除了一个 默认构造的 array 对象不为空,而 swap 确实 没有恒定的复杂性。 [...]

(强调我的)

标准没有提到这个非空array的元素是如何初始化的。


假设std::array 是这样实现的(对于N == 2):

template <class T, std::size_t N>
struct array {
    T __elem[N];
};

然后根据[dcl.init.aggr]/8{}复制初始化元素:

如果列表中的 initializer-clauses 少于 非联合聚合中的元素,然后每个元素不明确 initialized 初始化如下:

  • (8.1) 如果元素具有默认成员初始化程序 ([class.mem]),则从该初始化程序初始化元素。

  • (8.2) 否则,如果元素不是引用,从空的初始化列表 ([dcl.init.list]) 复制初始化元素。

  • (8.3) 否则,程序格式错误。

[...]

根据[dcl.init.list]/3 的格式不正确(选择删除的默认构造函数)。

这意味着标准认为常见的实现是错误的,这可能不是有意的。

【讨论】:

    猜你喜欢
    • 2018-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-18
    • 1970-01-01
    • 2014-11-30
    相关资源
    最近更新 更多