新数组分配

New array allocations

所以我有这个 class 调用点,只是为了在每次构造和销毁对象时记录到控制台。我做了以下事情:

#include <iostream>

struct point{
    point() {
        std::cout << "ctor\n";
    }
    ~point() {
        std::cout << "dtor\n";
    }
};
    int main(){
    int x = 3;
    point* ptr = new point[x]{point()};
    delete[] ptr;
}
ctor
dtor
dtor
dtor

这最终只调用了构造函数一次,而析构函数调用了 3 次,为什么?我知道这很糟糕,可能是 ub,但我想了解原因。其他分配给我 "expected" 输出:

int x = 3;
constexpr int y = 3;
point* ptr1 = new point[3];
point* ptr2 = new point[x];
point* ptr3 = new point[y]{point()};
ctor
ctor
ctor
dtor
dtor
dtor

我正在使用 visual studio 19 最新版本。

This ended up calling the constructor just once, and the destructor 3 times, why? 

您还需要记录复制和移动构造函数:

#include <iostream>

struct point{
    point() {
        std::cout << "default ctor\n";
    }
    point(const point &) {
        std::cout << "copy ctor\n";
    }
    point(point &&) {
        std::cout << "move ctor\n";
    }
    ~point() {
        std::cout << "dtor\n";
    }
};

这可能不会给你任何直接的答案,而只是提出一些观察结果。

正如@interjay 评论的那样,我们可以看到同一段代码在 VC++ 和其他流行编译器中的运行方式存在差异。我没有看到 MSDN 中提到任何偏离标准的情况,事实上 VC++ 确实支持 the standards in VS2017.

之后的列表和其他 iniliazilers

现在,在尝试重写同一事物的不同符号时,我观察到了一些有趣的东西。

验证 MSDN 中记录的内容我看到以下符号,

point* ptr = new point[x]{};

输出符合预期,并且在功能上它在不同的编译器中是一致的。

ctor
ctor
ctor
dtor
dtor
dtor

还有以下基于堆栈的数组分配,

 point ptr[3] = {point()};

得到我们

ctor
ctor
ctor
dtor
dtor
dtor

到目前为止一切看起来都很好,结果与其他编译器匹配,直到我们调用以下代码:

point* ptr = new point[x]{point()};

据我所知,这可能是 VC++ 中的一个实现故障。

当您调用 new 运算符以及大括号内的构造函数初始化时,可能是 VC++ 这不是推荐的用法,并且在内部它会回退一些自定义初始化处理程序,当为每个索引显式分配时,预计会调用构造函数,而其余所有内容都只分配未初始化的内存(没有调用构造函数)。这是一个明显的未定义行为 (UB)。

虽然,在我之前展示的几个示例中,看起来(查看 Windbg 中的调用堆栈,我对此可能完全错了)内部 VC++ 在运行时调用类似于向量构造函数初始值设定项,它(仅)迭代地为所有索引调用默认构造函数。

同样可以使用以下代码验证这一点,其中调用构造函数重载

 point(int i) {
            std::cout << "ctor "<<i<<"\n";
        }

来自

point ptr[3] = {point(10)}; //not a default constructor

吐槽:

ctor 10
ctor
ctor
dtor
dtor
dtor

这是一种奇怪且异常的行为,这在 VC++ 中并不是什么新鲜事。我可以建议的是尝试使用以下表示法在不同的编译器中获得一致的结果:

point* ptr = new point[x]{};

这是一个编译器错误。

通过使用没有常量定义类型大小的 operator new,MSVC 编译器将调用 class 对象构造函数和析构函数,次数与初始化列表 and/or 数组大小中明确指定的次数相同。

#include <iostream>

struct point {
    point() {
        std::cout << "ctor\n";
    }
    ~point() {
        std::cout << "dtor\n";
    }
};
int main() {
    int x = 3;
    point* ptr = new point[x]{point()};
    delete[] ptr;
}

如前所述将调用明确指定 point ctor 一次。

这可以通过以下方式断言:point* ptr = new point[x]{point(), point()};

  • MSVC 输出:ctor ctor dtor dtor dtor.
  • GCC:ctor ctor ctor dtor dtor dtor(应该保证)

甚至可抛出的数组越界异常 UB:point* ptr = new point[x]{point(), point(), point(), point(), point() }; 也遵循该行为。

  • MSVC 输出:ctor ctor ctor ctor ctor dtor dtor dtor.
  • 海湾合作委员会:terminate called after throwing an instance of 'std::bad_array_new_length'

如果定义的大小不变,则会正确检测到过多的初始化程序。即 const int x = 3constexpr int x = 3