为什么C++ 11类内初始值设定项不能使用括号?

del*_*rst 29 c++ c++11

例如,我不能这样写:

class A
{
    vector<int> v(12, 1);
};
Run Code Online (Sandbox Code Playgroud)

我只能写这个:

class A
{
    vector<int> v1{ 12, 1 };
    vector<int> v2 = vector<int>(12, 1);
};
Run Code Online (Sandbox Code Playgroud)

对C++ 11语言设计的差异有何考虑?

Nik*_*iou 29

这一选择背后的基本原理在相关明确提到提案非静态数据成员的初始化:

Kona在标识符范围方面提出的问题:

在2007年9月Kona会议的核心工作组讨论期间,出现了关于初始化程序中标识符范围的问题.我们是否希望允许类范围具有正向查找的可能性; 或者我们是否要求在解析它们时明确定义初始化器?

你想要什么:

类范围查找的动机是我们希望能够将任何内容放入非静态数据成员的初始化程序中,我们可以将其放入mem-initializer而不会显着改变语义(模数直接初始化与复制初始化) :

int x();

struct S {
    int i;
    S() : i(x()) {} // currently well-formed, uses S::x()
    // ...
    static int x();
};

struct T {
    int i = x(); // should use T::x(), ::x() would be a surprise
    // ...
    static int x();
};
Run Code Online (Sandbox Code Playgroud)

问题1:

不幸的是,这使得"(表达式列表)"的初始化器在解析声明时形式不明确:

   struct S {
        int i(x); // data member with initializer
        // ...
        static int x;
    };

    struct T {
        int i(x); // member function declaration
        // ...
        typedef int x;
    };
Run Code Online (Sandbox Code Playgroud)

一种可能的解决方案是依赖现有规则,如果声明可以是对象或函数,那么它是一个函数:

 struct S {
        int i(j); // ill-formed...parsed as a member function,
                  // type j looked up but not found
        // ...
        static int j;
    };
Run Code Online (Sandbox Code Playgroud)

一个类似的解决方案是应用另一个现有的规则,目前只在模板中使用,如果T可以是一个类型或其他东西,那么它就是其他东西; 如果我们真的是一个类型,我们可以使用"typename":

struct S {
        int i(x); // unabmiguously a data member
        int j(typename y); // unabmiguously a member function
    };
Run Code Online (Sandbox Code Playgroud)

这两种解决方案都引入了很多可能被许多用户误解的细微问题(comp.lang.c ++上的许多问题都证明了为什么"int i();"在块范围内没有声明默认初始化的int) .

本文提出的解决方案是仅允许"= initializer-clause"和"{initializer-list}"形式的初始化器.这解决了大多数情况下的歧义问题,例如:

HashingFunction hash_algorithm{"MD5"};
Run Code Online (Sandbox Code Playgroud)

在这里,我们不能使用=形式,因为HasningFunction的构造函数是显式的.在特别棘手的情况下,可能必须提到两次类型.考虑:

   vector<int> x = 3; // error:  the constructor taking an int is explicit
   vector<int> x(3);  // three elements default-initialized
   vector<int> x{3};  // one element with the value 3
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们必须使用适当的表示法在两种备选方案之间进行选择:

vector<int> x = vector<int>(3); // rather than vector<int> x(3);
vector<int> x{3}; // one element with the value 3
Run Code Online (Sandbox Code Playgroud)

问题2:

另一个问题是,因为我们建议不要更改初始化静态数据成员的规则,所以添加static关键字可能会使格式良好的初始化程序形成错误:

   struct S {
               const int i = f(); // well-formed with forward lookup
        static const int j = f(); // always ill-formed for statics
        // ...
        constexpr static int f() { return 0; }
    };
Run Code Online (Sandbox Code Playgroud)

问题3:

第三个问题是类范围查找可能会将编译时错误转换为运行时错误:

struct S {
    int i = j; // ill-formed without forward lookup, undefined behavior with
    int j = 3;
};
Run Code Online (Sandbox Code Playgroud)

(除非被编译器捕获,否则我可能会使用未定义的j值进行初始化.)

提案:

CWG在Kona进行了6比3的民意调查,支持分类范围查询; 这就是本文提出的内容,非静态数据成员的初始化程序仅限于"= initializer-clause"和"{initializer-list}"形式.

我们相信:

问题1:由于我们不提出()表示法,因此不会出现此问题.=和{}初始值表示法不会遇到此问题.

问题2:添加静态关键字会产生许多差异,这是最不重要的.

问题3:这不是一个新问题,但与构造函数初始化程序已存在的初始化顺序问题相同.

  • +1用于挖掘和格式化SO. (4认同)

Pra*_*ian 22

一个可能的原因是,允许括号会立即引导我们回到最棘手的解析.考虑以下两种类型:

struct foo {};
struct bar
{
  bar(foo const&) {}
};
Run Code Online (Sandbox Code Playgroud)

现在,您有一个bar要初始化的类型的数据成员,因此您将其定义为

struct A
{
  bar B(foo());
};
Run Code Online (Sandbox Code Playgroud)

但是你上面所做的是声明一个名为的函数B,它bar按值返回一个对象,并且接受一个带有签名的函数的参数foo()(返回一个foo并且不带任何参数).

从StackOverflow上处理这个问题的问题的数量和频率来看,这是大多数C++程序员发现令人惊讶和不直观的事情.添加新的大括号或等于初始化器语法是一个避免这种模糊性的机会,并从一个干净的平板开始,这可能是C++委员会选择这样做的原因.

bar B{foo{}};
bar B = foo();
Run Code Online (Sandbox Code Playgroud)

上面的两行都按预期声明了一个名为Btype 的对象bar.


除了上面的猜测,我想指出你在上面的例子中做了两件截然不同的事情.

vector<int> v1{ 12, 1 };
vector<int> v2 = vector<int>(12, 1);
Run Code Online (Sandbox Code Playgroud)

第一行初始化v1为包含两个元素的向量,121.第二个创建一个v2包含12元素的向量,每个元素都初始化为1.

注意这个规则 - 如果一个类型定义了一个构造函数initializer_list<T>,那么当该类型的初始化程序是一个braced-init-list时,该构造函数总是被认为是第一个.其他构造函数只有在采用不可行的构造函数时才会被考虑.initializer_list