() 和 {} 在使用 "new" 进行初始化时是否总是等价的?

Are () and {} always equivalent when used for initialization with "new"?

有一个 post 在使用 new 时处理 parentheses or not after the type name。但是这个呢:

如果'Test'是普通的class,有什么区别:

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

此外,假设 Test2 有一个类型为 Value 的参数的构造函数,它是否总是等同于写:

Value v;
Test2 *test2 = new Test(v);
// and
Test2 *test2 = new Test{v};

涉及 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
}

案例 2:(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 ctor 被忽略)

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

其中 testTest 类型的默认初始化对象,test2 是函数声明。

没有!

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

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

并且:

[C++11: 5.3.4/15]: A new-expression that creates an object of type T initializes that object as follows:

  • If the new-initializer is omitted, the object is default-initialized (8.5); if no initialization is performed, the object has indeterminate value.
  • Otherwise, the new-initializer is interpreted according to the initialization rules of 8.5 for direct-initialization.

并且:

[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]: The semantics of initializers are as follows. [..]

  • If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized (8.5.4).
  • [..]
  • If the initializer is (), the object is value-initialized.
  • [..]
  • If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
  • [..]

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

考虑到 list-initialisation 的规则,我不会在这里重现,这两个初始化器具有基本相同的效果 if T 没有采用 std::initializer_list<>.

的构造函数

一般的答案是否定的。使用 braced-init-list 作为初始化器将首先尝试解析为采用 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):

  • All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have default values, are examined, and matched by overload resolution against a single argument of type std::initializer_list
  • If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments that consists of the elements of the braced-init-list, with the restriction that only non-narrowing conversions are allowed. If this stage produces an explicit constructor as the best match for a copy-list-initialization, compilation fails (note, in simple copy-initialization, explicit constructors are not considered at all)