在构造函数中使用函数时收到未初始化的字段警告

getting an uninitialized field warning, when using functions inside the constructor

我的 class 的构造函数初始化初始化列表中的前 3 个字段,但对于 salary 字段,我需要使用 void calculateSalary() 函数。 (教授要求)代码工作得很好,但我收到警告:Clang-Tidy: Constructor does not initialize fields: salary 我很难理解为什么会收到此警告,因为我可以在构造函数的主体中初始化薪水r 值而不会出现错误,例如 salary = 5*year;,而我正在调用的函数正是这样做的。我不确定这是否是一个我可以忽略的冗余警告,或者这是否会以某种方式影响我的代码?

class Employee
{
private:
    std::string name;
    std::string surname;
    int year;
    double salary;
public:
    Employee(int year, std::string name, std::string surname)
            : year(year), name(std::move(name)), surname(std::move(surname))
    {
        calculateSalary();
    }

    void calculateSalary()
    {
        salary = 2310 + 2310 * year * 12 / 100.0;
    }
};

从字面上看,这似乎是 clang-tidy 发出的警告。如您所见,here 完整的诊断是:

<source>:11:5: warning: constructor does not initialize these fields: salary [cppcoreguidelines-pro-type-member-init,hicpp-member-init]
    Employee(int year, std::string name, std::string surname)
    ^

如果将 salary = ... 语句直接移动到构造函数中而不是调用函数,则会得到类似但不同的诊断结果:

<source>:14:9: warning: 'salary' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer]
        salary = 2310 + 2310 * year * 12 / 100.0;
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

这两个都参考了C++ core guidelines,一组编写C++的基本建议。

Here is clang's documentation for the first diagnostic and here 第二个。两者都符合 C++ 代码准则。link。

在第一个 link 中,如果通过函数调用而不是直接在构造函数中 initialization/assignment 分配成员,则第一次检查会给出误报。

正如您在 link 中看到的那样,如果不遵守,这两个诊断都会警告的指导方针的建议是初始化构造函数的成员初始化列表中的所有成员,或者自 C++11 起,如果有意义的话,在 class 定义本身的默认成员初始值设定项中。

如果不遵循这些建议,程序的行为不会有什么不同,但代码的可读性会降低,并且更改代码更有可能会意外导致缺少字段的初始化。

如果类型更复杂(即 class 具有 non-trivial 构造函数的类型),那么不在默认成员初始化程序或构造函数初始化程序列表中初始化也可能存在问题,因为这意味着默认构造后跟成员赋值,而不是直接正确构造。


Clang-tidy 为您的代码提供另一个重要诊断:

<source>:12:15: warning: field 'year' will be initialized after field 'name' [clang-diagnostic-reorder-ctor]
            : year(year), name(std::move(name)), surname(std::move(surname))
              ^~~~~~~~~~  ~~~~~~~~~~~~~~~~~~~~~  ~~~~~~~~~~~~~~~~~~~~~~~~~~~
              name(std::move(name)) surname(std::move(surname)) year(year)

构造函数中初始化程序的顺序错误。当 class 对象被初始化时,成员总是按照它们声明的顺序被初始化, 而不是 它们在构造函数的初始化列表中列出的顺序。因此,不复制完全相同的顺序很容易导致问题,例如,当一个成员的初始化取决于另一个成员的初始化已经完成并且构造函数被编写为使其 看起来 就像那样事实并非如此。


在上面的 link 中,我启用了所有 clang-tidy 检查。其中许多对于通用代码来说并不重要,甚至根本没有意义,例如llvmlibc-* 检查你可以在 link 中看到。我不能说您的构建中启用了哪些检查。可能只启用了第一个而没有启用第二个。如果你没有自己启用检查,你应该问问谁做了,为什么某些检查应该被认为是相关的,而其他的则不是。您可以通过上面的 links 找到核心指南的建议。我认为它们在很大程度上被广泛接受。


如果您想遵循核心指南建议并仍然将计算分离到一个函数中,您应该使用函数 return 初始化成员的值:

class Employee
{
private:
    std::string name;
    std::string surname;
    int year;
    double salary;

    double calculateSalary() const
    {
        return 2310 + 2310 * year * 12 / 100.0;
    }

public:
    Employee(int year, std::string name, std::string surname)
            : name(std::move(name)), surname(std::move(surname)), year(year), salary(calculateSalary())
    {
    }
};

我还制作了函数 private,因为它只是 class 的辅助函数。用户不应该调用它。