简体   繁体   English

为什么 C++11 类内初始化程序不能使用括号?

[英]Why C++11 in-class initializer cannot use parentheses?

For example, I cannot write this:例如,我不能这样写:

class A
{
    vector<int> v(12, 1);
};

I can only write this:我只能这样写:

class A
{
    vector<int> v1{ 12, 1 };
    vector<int> v2 = vector<int>(12, 1);
};

What's the consideration for the differences in C++11 language design? C++11语言设计的差异有哪些考虑?

The rationale behind this choice is explicitly mentioned in the related proposal for non static data member initializers :非静态数据成员初始化器的相关提案中明确提到了这种选择背后的基本原理:

An issue raised in Kona regarding scope of identifiers: Kona 中提出的关于标识符范围的问题:

During discussion in the Core Working Group at the September '07 meeting in Kona, a question arose about the scope of identifiers in the initializer.在 07 年 9 月的 Kona 会议上,核心工作组的讨论中出现了一个关于初始化程序中标识符范围的问题。 Do we want to allow class scope with the possibility of forward lookup;我们是否希望允许类范围具有向前查找的可能性? or do we want to require that the initializers be well-defined at the point that they're parsed?还是我们想要求初始化器在解析时定义良好?

What's desired:想要什么:

The motivation for class-scope lookup is that we'd like to be able to put anything in a non-static data member's initializer that we could put in a mem-initializer without significantly changing the semantics (modulo direct initialization vs. copy initialization):类范围查找的动机是我们希望能够将任何东西放入非静态数据成员的初始化程序中,我们可以将其放入内存初始化程序中而不会显着改变语义(模直接初始化与复制初始化) :

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

Problem 1:问题1:

Unfortunately, this makes initializers of the “( expression-list )” form ambiguous at the time that the declaration is being parsed:不幸的是,这使得“(表达式列表)”形式的初始化器在解析声明时变得模棱两可:

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

    struct T {
        int i(x); // member function declaration
        // ...
        typedef int x;
    };

One possible solution is to rely on the existing rule that, if a declaration could be an object or a function, then it's a function:一种可能的解决方案是依赖于现有规则,即如果声明可以是对象或函数,那么它就是函数:

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

A similar solution would be to apply another existing rule, currently used only in templates, that if T could be a type or something else, then it's something else;一个类似的解决方案是应用另一个现有规则,目前仅在模板中使用,如果 T 可以是类型或其他东西,那么它就是其他东西; and we can use “typename” if we really mean a type:如果我们真的指的是类型,我们可以使用“typename”:

struct S {
        int i(x); // unabmiguously a data member
        int j(typename y); // unabmiguously a member function
    };

Both of those solutions introduce subtleties that are likely to be misunderstood by many users (as evidenced by the many questions on comp.lang.c++ about why “int i();” at block scope doesn't declare a default-initialized int).这两种解决方案都引入了许多用户可能会误解的微妙之处(comp.lang.c++ 上关于为什么“int i();”在块范围内没有声明默认初始化的 int 的许多问题证明了这一点) .

The solution proposed in this paper is to allow only initializers of the “= initializer-clause” and “{ initializer-list }” forms .本文提出的解决方案是只允许“= initializer-clause”和“{ initializer-list }”形式的初始化程序 That solves the ambiguity problem in most cases, for example:这解决了大多数情况下的歧义问题,例如:

HashingFunction hash_algorithm{"MD5"};

Here, we could not use the = form because HasningFunction's constructor is explicit.在这里,我们不能使用 = 形式,因为 HasningFunction 的构造函数是显式的。 In especially tricky cases, a type might have to be mentioned twice.在特别棘手的情况下,一个类型可能不得不被提及两次。 Consider:考虑:

   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

In that case, we have to chose between the two alternatives by using the appropriate notation:在这种情况下,我们必须通过使用适当的符号在两个备选方案之间进行选择:

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

Problem 2:问题2:

Another issue is that, because we propose no change to the rules for initializing static data members, adding the static keyword could make a well-formed initializer ill-formed:另一个问题是,因为我们建议不对初始化静态数据成员的规则进行更改,添加 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; }
    };

Problem 3:问题3:

A third issue is that class-scope lookup could turn a compile-time error into a run-time error:第三个问题是类范围查找可能会将编译时错误变成运行时错误:

struct S {
    int i = j; // ill-formed without forward lookup, undefined behavior with
    int j = 3;
};

(Unless caught by the compiler, i might be intialized with the undefined value of j.) (除非被编译器捕获,否则 i 可能会使用未定义的 j 值进行初始化。)

The proposal:提案:

CWG had a 6-to-3 straw poll in Kona in favor of class-scope lookup; CWG 在 Kona 进行了 6 比 3 的投票,支持类范围查找; and that is what this paper proposes, with initializers for non-static data members limited to the “= initializer-clause” and “{ initializer-list }” forms.这就是本文的建议,非静态数据成员的初始化器仅限于“= initializer-clause”和“{ initializer-list }”形式。

We believe:我们相信:

Problem 1: This problem does not occur as we don't propose the () notation.问题 1:这个问题不会发生,因为我们不建议使用 () 表示法。 The = and {} initializer notations do not suffer from this problem. = 和 {} 初始化符号不会遇到这个问题。

Problem 2: adding the static keyword makes a number of differences, this being the least of them.问题 2:添加 static 关键字会产生许多差异,这是其中最小的一个。

Problem 3: this is not a new problem, but is the same order-of-initialization problem that already exists with constructor initializers.问题 3:这不是一个新问题,而是与构造函数初始化程序已经存在的初始化顺序问题相同。

One possible reason is that allowing parentheses would lead us back to the most vexing parse in no time.一个可能的原因是,允许使用括号会导致我们很快回到最令人头疼的解析 Consider the two types below:考虑以下两种类型:

struct foo {};
struct bar
{
  bar(foo const&) {}
};

Now, you have a data member of type bar that you want to initialize, so you define it as现在,您有一个要初始化的bar类型的数据成员,因此您将其定义为

struct A
{
  bar B(foo());
};

But what you've done above is declare a function named B that returns a bar object by value, and takes a single argument that's a function having the signature foo() (returns a foo and doesn't take any arguments).但是您在上面所做的是声明一个名为B的函数,该函数按值返回一个bar对象,并接受一个参数,该参数是一个具有签名foo()的函数(返回一个foo并且不接受任何参数)。

Judging by the number and frequency of questions asked on StackOverflow that deal with this issue, this is something most C++ programmers find surprising and unintuitive.从 StackOverflow 上针对此问题提出的问题的数量和频率来看,这是大多数 C++ 程序员发现的令人惊讶和不直观的问题。 Adding the new brace-or-equal-initializer syntax was a chance to avoid this ambiguity and start with a clean slate, which is likely the reason the C++ committee chose to do so.添加新的大括号或相等初始化器语法是避免这种歧义并从头开始的机会,这可能是 C++ 委员会选择这样做的原因。

bar B{foo{}};
bar B = foo();

Both lines above declare an object named B of type bar , as expected.正如预期的那样,上面的两行都声明了一个名为Bbar类型的对象。


Aside from the guesswork above, I'd like to point out that you're doing two vastly different things in your example above.除了上面的猜测之外,我想指出您在上面的示例中做了两件截然不同的事情。

vector<int> v1{ 12, 1 };
vector<int> v2 = vector<int>(12, 1);

The first line initializes v1 to a vector that contains two elements, 12 and 1 .第一行将v1初始化为一个包含两个元素121的向量。 The second creates a vector v2 that contains 12 elements, each initialized to 1 .第二个创建一个包含12元素的向量v2 ,每个元素都初始化为1

Be careful of this rule - if a type defines a constructor that takes an initializer_list<T> , then that constructor is always considered first when the initializer for the type is a braced-init-list .请注意这条规则 - 如果一个类型定义了一个采用initializer_list<T>的构造函数,那么当该类型的初始值设定项是一个花括号初始化列表时,总是首先考虑该构造函数。 The other constructors will be considered only if the one taking the initializer_list is not viable.仅当采用initializer_list的构造函数不可行时,才会考虑其他构造函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM