C++-带有指针的Weffc++警告

C++ -Weffc++ warning with pointers

本文关键字:Weffc++ 警告 指针 C++-      更新时间:2023-10-16

我很难理解这个错误。我正在使用-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

这是一个警告,而不是错误。在拥有默认构造函数的情况下,有些指针往往无法正确使用。如果希望警告消失,请定义构造函数和赋值运算符。三/五/零规则

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

如果不这样做,移动和复制类的实例可能会导致默认实例化构造函数/赋值运算符产生不必要的影响,例如复制/移动无法复制/移动的资源。看看析构函数,想想如果让默认方法处理指针会发生什么。

使用B,编译器可以检测到可能违反"三条规则",并发出有效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); 

在没有有效C++警告的情况下编译,就像A一样。我很长一段时间都无法理解这件事。这就提出了一个重要的问题。警告是由实施者提供的。如果有些事情很难或不可能证明,那就没有任何警告。

但为什么这个警告很严厉?

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

我怀疑GCC在发现至少一个特殊成员函数而不是全部时触发了有效C++警告。

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

所以

struct C
{
C() :second(dummy)
{
}
~C() // force a destructor
{
}
int * first = nullptr;
std::vector<int>& second;
};

应该并且确实提出有效的C++警告。

注意:生成的完全琐碎的复制构造函数

C c; 
C d(c); 

这本身似乎并没有引起警告。也没有提供复制构造函数。警告的钩子可能只在析构函数上,导致返回关于仅在Implementor的宽限期内存在的警告的警告。