简体   繁体   English

添加initializer_list构造函数后,静默断开构造函数调用

[英]Silent breaking of constructor calls after adding initializer_list constructor

Let's consider the following: 我们考虑以下几点:

#include <iostream>
#include <initializer_list>

class Foo {
public:
    Foo(int) {
        std::cout << "with int\n";
    }
};

int main() {
    Foo a{10};  // new style initialization
    Foo b(20);  // old style initialization
}

Upon running it prints: 在运行它打印:

with int
with int

All good. 都好。 Now due to new requirements I have added a constructor which takes an initializer list. 现在由于新的要求,我添加了一个构造函数,它带有一个初始化列表。

Foo(std::initializer_list<int>) {
    std::cout << "with initializer list\n";
}

Now it prints: 现在它打印:

with initializer list
with int

So my old code Foo a{10} got silently broken. 所以我的旧代码Foo a{10}被默默地打破了。 a was supposed to be initialized with an int . a应该用int初始化。

I understand that the language syntax is considering {10} as a list with one item. 我理解语言语法正在考虑将{10}作为包含一个项目的列表。 But how can I prevent such silent breaking of old code? 但是,如何防止旧代码的这种无声破坏呢?

  1. Is there any compiler option which will give us warning on such cases? 是否有任何编译器选项会在这种情况下向我们发出警告? Since this will be compiler specific I'm mostly interested with gcc. 由于这将是编译器特定的,我最感兴趣的是gcc。 I have already tried -Wall -Wextra . 我已经尝试过-Wall -Wextra
  2. If there is no such option then do we always need to use old style construction, ie with () Foo b(20) , for other constructors and use {} only when we really meant an initializer list? 如果没有这样的选项,那么我们是否总是需要使用旧样式构造,即使用() Foo b(20) ,对于其他构造函数,并且仅当我们真正意味着初始化列表时才使用{}

It's impossible to generate any warning in these cases, because presented behaviour of choosing std::initializer_list constructor over direct matches is well defined and compliant with a standard. 在这些情况下不可能生成任何警告,因为在直接匹配中选择std::initializer_list构造函数的行为已明确定义并符合标准。

This issue is described in detail in Scott Meyers Effective Modern C++ book Item 7: 这个问题在Scott Meyers Effective Modern C ++ book Item 7中有详细描述

If, however, one or more constructors declare a parameter of type std::initializer_list , calls using the braced initialization syntax strongly prefer the overloads taking std::initializer_lists. 但是,如果一个或多个构造函数声明了std::initializer_list类型的参数,则使用支撑初始化语法的调用非常喜欢使用std :: initializer_lists的重载。 Strongly. 强烈。 If there's any way for compilers to construe a call using a braced initializer to be to a constructor taking a std::initializer_list , compilers will employ that interpretation. 如果编译器有任何方法将使用支撑初始化程序的调用解释为采用std::initializer_list的构造std::initializer_list ,则编译器将采用该解释。

He also presents a few of edge cases of this issue, I strongly recommend reading it. 他还提出了一些关于这个问题的边缘案例,我强烈建议你阅读它。

I couldn't find such an option, so apparently in such cases you should use parentheses for classes that have initializer_list constructor, and uniform initialization for all other classes as you wish. 我找不到这样的选项,所以显然在这种情况下你应该使用括号用于具有initializer_list构造函数的类,并根据需要对所有其他类进行统一初始化。

Some useful insights can be found in this answer and comments to it: https://stackoverflow.com/a/18224556/2968646 一些有用的见解可以在这个答案和评论中找到: https//stackoverflow.com/a/18224556/2968646

There are no compiler warnings and there never will be. 没有编译器警告,也永远不会。 It just doesn't make sense to warn on code doing something common like 警告代码做一些常见的事情是没有意义的

std::vector vec{1};

Remember that the compiler only warns about really unwanted stuff, like undefined behavior. 请记住,编译器只会警告真正不需要的东西,比如未定义的行为。 It has no way of knowing that in the definition above, you meant to call the constructor taking a size argument. 它无法知道在上面的定义中,你的意思是调用构造函数来获取一个size参数。 For all it knows you actually want to have a vector with one element! 事实上,它知道你实际上想要一个带有一个元素的向量! It can't read your mind :) 它无法读懂你的想法:)

The answer to your second question is basically yes. 你的第二个问题的答案基本上是肯定的。 You can always add a dummy parameter like struct {} dummy; 您总是可以添加一个虚拟参数,如struct {} dummy; to avoid using the constructor with initializer list, but really, the only same solution is just to use parentheses instead of braces (or don't break the interface suddenly). 避免使用带有初始化列表的构造函数,但实际上,唯一相同的解决方案只是使用括号而不是大括号(或者不要突然断开接口)。

If you want to change every portion of code that uses list initialization, you can delete the initializer list constructor, change them to braces, and then implement the constructor correctly. 如果要更改使用列表初始化的代码的每个部分,可以删除初始化列表构造函数,将它们更改为大括号, 然后正确实现构造函数。 I would consider such change a breaking one, and deal with it appropriately. 我会认为这种改变是破坏性的,并适当地处理它。 The other idea would have been to come up with the initializer list use case beforehand, and implement it right away. 另一个想法是事先想出初始化列表用例,并立即实现它。

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

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