Cppcheck与Clang-tidy:显式构造函数初始化列表

4
当我在下面的代码运行clang-tidy-3.8和cppcheck-1.72工具时:
#include <initializer_list>
#include <string>
#include <iostream>

using string_list = std::initializer_list<std::string>;

class Foo {
    public:
    explicit Foo(const string_list& strings) {
        for (const auto& ss : strings) {
            std::cout << ss << std::endl;
        }
    }
};

clang-tidy-3.8的输出:

$ > clang-tidy -checks='*' main.cpp -- -std=c++11

warning: initializer-list constructor should not be declared explicit [google-explicit-constructor] explicit Foo(const string_list& strings)

然而,如果我移除关键字explicit,cppcheck-1.72报告如下:

$ > cppcheck main.cpp --language=c++ --std=c++11 --enable=all

(style) Class 'Foo' has a constructor with 1 argument that is not explicit.

我在Google Cpp Guide中看到:

不能通过单个参数调用的构造函数通常应省略explicit。具有单个std :: initializer_list参数的构造函数也应省略explicit,以支持复制初始化(例如MyType m = {1, 2};)。

按照C++标准,哪个工具是正确的?


"Correct" 的意思是什么?你希望如何使用你的构造函数? - Kerrek SB
1
clang-tidy (3.8) 对于只有一个参数的构造函数必须显式声明和带有初始化列表的构造函数不应该显式声明都进行了测试,但是_cppcheck (1.73)_ 没有对初始化列表进行测试。因此,clang-tidy 显式地放宽了初始化列表的单个参数规则。 - t.niese
这个放宽的规则是来自C++11标准吗? - uilianries
@KerrekSB,"correct"(对我来说)意味着遵循C++标准。构造函数只是一个例子,然而,我可以创建一个类似的对象来接收真实对象,使用相同的实现方式。在这种情况下,我不知道这是否只是一种风格规则,由Google(clang-tidy)提出的,还是一条C++规则(cppcheck bug)。 - uilianries
3
C++标准允许在声明构造函数时加上或不加上explicit关键字,你代码中的两种变量都是符合标准的。这些工具检查的不是是否符合标准(编译器会检查),而是是否符合风格指南 - 显然是不同的指南。你一直在说cppcheck强制执行你所称的“C++规则”- 但请注意它的消息明确标记为(style) - Igor Tandetnik
谢谢,@IgorTandetnik!我阅读了标准,但没有找到关于这个的说明。不过,你的解释很清楚。 - uilianries
1个回答

1

正如@KerrekSB所说,这取决于您想要强制执行的构造样式。

如果您使初始化列表构造函数explicit,则:

  • 您将禁止使用YourType A = {a, b, c};语法。
  • 但只允许使用YourType A({a, b, c});(或YourType A{{a, b, c}};) (我认为有些编译器接受YourType A{a, b, c} 但我发现它不一致)。

如果您没有标记其为explict,则两种语法都被允许。

有些人主张永远不要在类的构造函数中使用=(即使是对于初始化列表参数),因此通过标记explicit最终实施的风格就是这样。

标记explicit的另一个重要副作用是,您不能将原始初始化列表作为函数参数传递,以代替构造的对象(这可能会有限制,但可以成为进一步样式考虑的一部分)。 例如:fun(arg1, arg2, {a, b, c})fun(arg1, arg2, YourType({a, b, c}))

还要注意,例如std::vector::vector(std::initializer_list)(或任何其他std容器)未标记为explicit


我的经验法则是,当右侧可以用构造类型“忠实”地表示且计算复杂度较低(例如小于O(N log N)或O(N^2))时,我允许在构造函数中使用=。 在我的看来,在初始化列表中很少有这样的情况。 我遇到的唯一例子是:1)数组或列表的某些重现(包括std::vector)2)无序线性容器(但在我看来不包括有序容器)。3)多维数组(嵌套的初始化列表)。4)元组(虽然语言中也有非同质的初始化列表)。

根据这个规则,我认为没有明确指出std::set是错误的,因为std::set会在幕后重新排序。


实际上我的做法是添加一条注释来抑制cppcheck警告,我觉得对于任何隐式单参数构造函数,添加注释都是必要的。

    // cppcheck-suppress noExplicitConstructor ; because human-readable explanation here
    YourType(std::initializer_list<value_type> il){...}

并使用选项--inline-supp运行cppcheck

(请参见http://cppcheck.sourceforge.net/manual.pdf#page=19


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