新数组分配

问题描述 投票:6回答:3

因此,我有一个称为point的类,每次构造和销毁对象时,它仅登录到控制台。我做了以下工作:

#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

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

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最新版本。

c++ arrays new-operator
3个回答
1
投票

这是编译器错误。

通过使用操作符new而不使用常量定义的类型大小,MSVC编译器将在初始化器列表和/或数组大小中显式指定的多次调用类对象的构造函数和析构函数。

#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;
}

如上所述,将被称为明确指定pointctor一次。

这可以通过: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
  • GCC:terminate called after throwing an instance of 'std::bad_array_new_length'

如果定义的大小为常数,则正确检测到太多初始化程序。即const int x = 3constexpr int x = 3


0
投票

这可能不会给您直接的答案,而只是提出一些意见。

正如@interjay所评论的,我们可以看到,同一段代码在VC ++和其他流行的编译器中的运行方式有所不同。我没有看到MSDN中提到过任何与标准的偏离,事实上,VC ++确实支持the standards in VS2017之后的列表和其他别名。

现在,当使用不同的符号重写同一件事时,我观察到的东西很有趣。验证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]{};

-2
投票

最终只调用了一次构造函数,而两次调用了析构函数,为什么?

您还需要记录副本并移动构造函数:

#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";
    }
};
© www.soinside.com 2019 - 2024. All rights reserved.