当使用"new"进行初始化时,()和{}是否始终等效?

11

有一篇文章涉及使用new时类型名称后是否需要括号(type name后面加括号与否)。但是对于以下情况呢:

如果'Test'是一个普通类,那么下面两种方式有什么区别:

Test* test = new Test();
// and
Test* test = new Test{};

此外,假设Test2具有一个以Value类型的参数为构造函数,那么写成下面这样是否始终等效:
Value v;
Test2 *test2 = new Test(v);
// and
Test2 *test2 = new Test{v};
3个回答

10

涉及到 std::initializer_list<> 时,可能会存在不同的情境,例如:

情境1 - (){}

#include <initializer_list>
#include <iostream>
using namespace std;

struct Test2 {
    Test2(initializer_list<int> l) {}
};

int main() {
    Test2* test3 = new Test2(); // compile error: no default ctor
    Test2* test4 = new Test2{}; // calls initializer_list ctor
}

情况二: (v){v}

struct Value {
};

struct Test3 {
    Test3(Value v) {}
    Test3(initializer_list<Value> v) {}
};

int main() {
    Value v;
    Test3* test5 = new Test3(v); // calls Test3(Value)
    Test3* test6 = new Test3{v}; // calls Test3(initializer_list<Value>)
}

正如Meyers和其他人所说,使用STL时也有很大的区别:

    using Vec = std::vector<int>;
    Vec* v1 = new Vec(10); // vector of size 10 holding 10 zeroes
    Vec* v2 = new Vec{10}; // vector of size 1 holding int 10

它不仅限于使用 std::vector

在这种情况下,虽然有initializer_list构造函数但没有区别

#include <initializer_list>
#include <iostream>
using namespace std;

struct Test {
    Test() {}
    Test(initializer_list<int> l) {}
};

int main() {
    Test* test1 = new Test(); // calls default ctor
    Test* test2 = new Test{}; // same, calls default ctor
}

在这种情况下还有一个众所周知的区别。

void f() {
    Test test{};
    Test test2();
}

test 是类型为 Test 的默认初始化对象,而 test2 则是一个函数声明。


这已经在这个问题的答案中被声明,而这个问题是“跟进”的。 - Lightness Races in Orbit
你说得对 - 如果初始化器是 _花括号初始化列表_,那么 直接初始化 本身会转而使用 _列表初始化_。 - Lightness Races in Orbit
Scott Meyers在他的《Effective Modern C++》一书中专门讲到了这个问题。 - Paul Rooney
是的,但这种情况确实被Meyers省略了 - 当()或{}中没有任何内容时。 - Peter K

6

不行!

new-initializer 可以采用以下形式:

new-initializer:
   ( expression-listopt)
   braced-init-list

还有:

[C++11: 5.3.4/15]: 创建类型为 T 的对象的 new-expression 执行以下初始化操作:

  • 如果省略了 new-initializer,则对象将进行 default-initialized (8.5);如果没有执行初始化,则对象具有不确定值。
  • 否则,将根据 direct-initialization 的 8.5 初始化规则解释 new-initializer

还有:

[C++11: 8.5/15]: The initialization that occurs in the forms

T x(a);
T x{a};

as well as in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization.

并且:

[C++11: 8.5/16]: 初始化器的语义如下。 [..]

  • 如果初始化器是(非括号)花括号初始化列表,则对象或引用将进行列表初始化(8.5.4)。
  • [..]
  • 如果初始化器是(),则对象将进行值初始化
  • [..]
  • 如果初始化是直接初始化,或者如果它是复制初始化,其中源类型的cv未限定版本与目标类相同或是其派生类,则考虑构造函数。 可适用的构造函数已枚举(13.3.1.3),并且通过重载决议(13.3)选择最佳构造函数。 所选的构造函数被调用以使用初始化表达式或表达式列表作为其参数来初始化对象。 如果没有构造函数适用,或者重载决议不明确,则初始化是非法的。
  • [..]

因此,您可以看到,在这种情况下(以及其他一些情况下),两个都被定义为直接初始化,但是进一步的规则意味着根据您是否使用(){}以及初始化程序是否为空,可能会发生不同的事情。

考虑列表初始化的规则,我不会在此处重复,如果T没有采用std :: initializer_list<>,则两个初始化程序实际上具有基本相同的效果。


1
...并且如果T不是一个聚合体。 - T.C.

5
一般来说,不需要。使用花括号列表初始化器将首先尝试解析到接受std::initializer_list参数的构造函数。以此为例:
#include <iostream>
#include <vector>
int main() {
  auto p = new std::vector<int>{1};
  auto q = new std::vector<int>(1);
  std::cout << p->at(0) << '\n';
  std::cout << q->at(0) << '\n';
}

请注意列表初始化的语义(引用自cppreference):
  • 所有只接受std::initializer_list作为参数,或者将其作为第一个参数且剩余参数具有默认值的构造函数,都会被检查,并与类型为std::initializer_list的单个参数进行重载决议匹配。
  • 如果前一阶段没有产生匹配项,则类型T的所有构造函数都参与重载决议,针对由大括号初始化列表元素组成的参数集进行匹配,但仅允许非收窄转换。如果此阶段为复制列表初始化选择了显式构造函数作为最佳匹配项,则编译失败(请注意,在简单的复制初始化中,根本不考虑显式构造函数)。

我认为在第一种情况下它们也是不同的。 - sp2danny
@sp2danny 列表初始化的语义确实很复杂。不同版本的C++也对此有不同的陈述。无论如何,我决定将答案保留为现在这样,因为它是日常编程中最相关的部分,人们应该熟悉它。但是,欢迎对这个答案进行编辑,使其更加准确。 - Lingxi

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