C++ -Weffc++ 警告与指针

C++ -Weffc++ warning with pointers

我很难理解这个错误。 我正在使用 -Weffc++ 标志进行编译。

此结构编译正常。

struct A
{
A(){}
int * first = nullptr;
int second = 0;
};

这不是编译。

struct B
{
B(){}
int * first = nullptr;
std::vector<int> second{};
};

我得到:

prog.cc:14:8: warning: 'struct B' has pointer data members [-Weffc++]
   14 | struct B
      |        ^
prog.cc:14:8: warning:   but does not override 'B(const B&)' [-Weffc++]
prog.cc:14:8: warning:   or 'operator=(const B&)' [-Weffc++]

但这又可以正常编译了。

struct C
{
int * first;
std::vector<int>& second;
};

为什么我们会收到关于指针的错误(它们在每个结构中)? 为什么添加 std::vector<int> 会引发错误? 我使用了最新的 gcc 9.00C++2a

这是警告,不是错误。在拥有默认构造函数时,您有一些指针不会正确处理。如果您希望警告消失,请定义构造函数和赋值运算符。 The rule of three/five/zero

struct B {
    int* first;
    std::vector<int> second;

    B() : first(nullptr), second{} {}  // default
    B(const B&) = delete;              // copy ctor
    B(B&&) = delete;                   // move ctor
    B& operator=(const B&) = delete;   // copy assignment
    B& operator=(B&&) = delete;        // move assignment  
    ~B() { delete[] first; }           // dtor
};

如果不这样做,移动和复制 class 的实例可能会对默认实例化的 constructors/assignment 运算符造成不良影响,例如 copying/moving 无法 [=18] 的资源=].看看析构函数,想想如果让默认方法处理指针会发生什么。

使用 B 时,编译器可以检测到可能违反了三规则并发出 Effective C 警告。从 Ted Lyngmo 对这个问题的回答开始,许多其他地方都很好地解决了这个问题。

但为什么其他两个不触发相同的警告?

C 让我们消除了一半的顾虑:引用成员变量不能被重新赋值,防止编译器生成一个默认的赋值运算符来引起任何麻烦。

C c; // uninitialized second. GCC misses this
C d;
c = d; //fails. deleted assignment operator

但是复制构造函数应该仍然是可能的并且是一个潜在的威胁。

C c; // uninitialized second. GCC misses this
C d(c); // but it does catch the uninitialized second if you do this

修改 C

std::vector<int> dummy;
struct C
{
    C() :second(dummy) // initialize second
    {

    }
    int * first = nullptr;
    std::vector<int>& second;
};

允许

C c; 
C d(c); 

A 一样在没有 Effective C++ 警告的情况下进行编译。很长一段时间我都无法解决这个问题。这提出了一个重要的观点。警告是由实施者的恩典给出的。如果事情很难或不可能证明,将不会有警告。

但为什么这个警告很难?

编译器必须知道寻找潜在的问题。这意味着它将寻找问题的特征。这意味着一个或多个可能需要特殊处理的成员、析构函数、复制构造函数或赋值运算符,至少没有三规则要求的其他两个特殊成员函数之一。

我怀疑 GCC 在找到至少一个特殊成员函数但不是全部时会触发 Effective C++ 警告。

让我们看看三个classes的析构函数。 Aint 不需要特殊的销毁逻辑。 C 的引用也没有。 Bvector 是另一回事。至少它需要释放一些存储空间。这需要编译器生成一些代码,一旦有一个多于一个什么都不做的析构函数,编译器就可以看到 class 有一个没有三规则的其他两个部分的析构函数,并且包含可能需要的成员特殊处理(指针)。

所以

struct C
{
    C() :second(dummy)
    {

    }
    ~C() // force a destructor
    {

    }
    int * first = nullptr;
    std::vector<int>& second;
};

应该并且确实会引发有效的 C++ 警告。

注意:

生成的非常简单的复制构造函数
C c; 
C d(c); 

似乎不​​会自行触发警告。也不提供复制构造函数。警告的挂钩可能仅在析构函数上,导致警告仅在实现者的恩典下才存在的警告。