2

I have a difficulty in understanding this error. I am compiling with -Weffc++ flag.

This structure is compiling fine.

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

This is NOT compiling.

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

I am getting:

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++]

But this is compiling fine again.

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

Why we are getting error about pointers (they are in each of the struct)? Why adding std::vector<int> invokes error? I used newest gcc 9.00 and C++2a

Newbie
  • 462
  • 7
  • 15
  • 1
    [Rule of 0/3/5](https://en.cppreference.com/w/cpp/language/rule_of_three) – Ted Lyngmo Dec 22 '18 at 00:17
  • Ignoring the Rules of Three and Five is why it's potentially bad, but smurfed if I know why all three cases don't all raise the same warning – user4581301 Dec 22 '18 at 00:51
  • @user4581301: In the other cases, those operators are deleted, so that's fine. – Mooing Duck Dec 22 '18 at 01:02
  • Technically, the code is compiling in all three cases - warnings are not diagnostics that prevent successful compilation (unless the compiler is directed to treat warnings as errors, which doesn't happen by default). In any event, the first example is a POD type, which is not a type that requires the rule of 3/5/0. – Peter Dec 22 '18 at 06:04
  • 1
    The true answer isn't just the Rule of Three, but that it is violating item 11 in the old edition of Effective C++: "Declare a copy constructor and an assignment operator for classes with dynamically allocated memory". See https://stackoverflow.com/a/11529328/10548514 – Juan Carlos Ramirez Apr 25 '19 at 21:44

2 Answers2

4

It's a warning, not an error. You have pointers that you don't tend to properly while having a default constructor. Define constructors and assignment operators if you want the warnings to go away. 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
};

If you don't, moving and copying instances of your class could cause unwanted effects by the default instantiated constructors/assignment operators like copying/moving resources not capable of being copied/moved. Take a look at the destructor and think what would happen if you let the default methods deal with the pointer.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
3

With B the compiler can detect that the Rule of Three is potentially being violated and raises the Effective C warning. Resolving this is covered well in many other places starting with Ted Lyngmo's answer to this question.

But why don't the other two trigger the same warning?

C allows us to eliminate half of the concern: The reference member variable cannot be re-assigned preventing the compiler from generating a default assignment operator to cause any grief.

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

but the copy constructor should still be possible and a potential threat.

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

Making a modification to C

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

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

Allows

C c; 
C d(c); 

to compile without the Effective C++ warning just like A. I couldn't wrap my head around this for a long while. This brings up an important point. Warnings are given by the grace of the Implementor. If something is hard or impossible to prove, there will be no warning.

But why is this warning hard?

The compiler has to know to look for a potential problem. This means it will be looking for the signature of the problem. This means one or more members that may require special handling, a destructor, copy constructor, or assignment operator without at least one of the other two special member functions required by the Rule of Three.

I suspect GCC is triggering the Effective C++ warning when it finds a at least one of the special member functions but not all.

Let's look at the destructors of the three classes. A's int requires no special destruction logic. Neither does a C's reference. B's vector is a different story. At the very least it needs to release some storage. This requires the compiler to generate some code, and once there's a more than a do-nothing destructor the compiler can see that the class has a destructor without the other two parts of the Rule of Three and contains members that may require special handling (the pointer).

So

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

    }
    ~C() // force a destructor
    {

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

Should, and does, raise the effective C++ warning.

Note: the utterly trivial copy constructor generated by

C c; 
C d(c); 

doesn't seem to set off the warning by itself. Nor does providing a copy constructor. The warning's hook may be solely on the destructor, leading back to the caveat about warnings existing only by the grace of the Implementor.

user4581301
  • 33,082
  • 7
  • 33
  • 54