What hides class Empty {}

    This is a note about the methods that C ++ creates automatically, even if you did not create them.

    Who is this note for? I hope that it will be interesting to novice C ++ programmers. And experienced programmers will once again refresh and systematize their knowledge.


    So if you wrote

    class Empty {};

    then, know that you actually created something like this class:

    class Empty {
    public:
      // Constructor without parameters
      Empty ();
      // Copy constructor
      Empty (const Empty &);
      // Destructor
      ~ Empty ();
      // Assignment Operator
      Empty & operator = (const Empty &);
      // Operator to get the address
      Empty * operator & ();
      // Operator to get the address of a constant object
      const Empty * operator & () const;
    };

    It must be remembered that these functions are always created, and can lead to unexpected results.

    How can this lead to trouble?


    The most unpleasant thing is when the program works, but not in the way you wanted. In this case, no errors in terms of language do not arise. Implicitly generated methods lead precisely to such troubles.

    Example One: Constructors


    Consider a class that displays messages about creating / deleting objects and supports a static object counter (for simplicity, in the form of a public int).

    class CC {
    public:
      CC ();
      ~ CC ();
      static int cnt;
    };

    The implementation is trivial:

    int CC :: cnt (0);
    CC :: CC () {cnt ++; cout << "create \ n";}
    CC :: ~ CC () {cnt--; cout << "destroy \ n";}

    What will such a program do?

    void f (CC o) {}
    int main () {
      CC o;
      cout << "cnt =" << o.cnt << "\ n";
      f (o);
      cout << "cnt =" << o.cnt << "\ n";
      f (o);
      cout << "cnt =" << o.cnt << "\ n";
      return 0;
    }

    The result may surprise an unprepared reader:

    create
     cnt = 1
    destroy
     cnt = 0
    destroy
     cnt = -1
    destroy

    One gets the feeling that the object was created only once, and deleted - three times. The object counter goes to minus. At the same time, the program quietly fulfills and does not crash anywhere.

    As you understand, this happened because we did not take into account the automatically created copy constructor, which only copies, but does not print anything or correct the counter.

    We can fix this if we add the copy constructor ourselves

    CC :: CC (const CC &) {
      cnt ++; cout << "create (copy) \ n";
    }

    Now we get an absolutely reasonable result:

    create
     cnt = 1
    create (copy)
    destroy
     cnt = 1
    create (copy)
    destroy
     cnt = 1
    destroy

    Similar ambushes arise at assignment (operator =); but...

    Example Two: Getting the Address


    ... the most, perhaps, exquisite dirty tricks can arise if you have implemented a non-trivial method of obtaining an address

    CC * operator & ();

    but they forgot to realize his double, possessing the same (or other?) non-trivial properties:

    const CC * operator & () const;

    So far, your program is limited to non-constant objects:

    SS o;
    CC * p;
    p = & o;

    everything works. This can go on for a very long time, everyone will already forget how the CC object is arranged, will be credited with it and will not think about it when errors occur.

    But sooner or later the code will appear:

    CC const o;
    CC const * q = & o;

    And the method

    CC * operator & ();

    It will not work treacherously (I already wrote about const overloading here ).

    But enough, probably, examples. The meaning of them all is about the same. How to avoid all the described troubles.

    It is very easy to insure yourself from these misunderstandings!


    The easiest way is to create prototypes of all methods created automatically and not create implementations.

    Then the program simply does not link and you will receive a completely reasonable message. I got this:

    /var/tmp//ccGQszLd.o(.text+0x314): In function `main ':
    : undefined reference to `CC :: operator & () const '

    Your compiler may put it a little differently.

    If this method seems clumsy to you (you have every reason and I agree with you), then you can create "full" methods, but make them private.

    Then you will also receive error messages at the compilation stage.

    count2.cpp: In function 'int main ()':
    count2.cpp: 22: error: 'const CC * CC :: operator & () const' is private
    count2.cpp: 37: error: within this context

    Agree that this message looks somehow ... decent.

    Well, the third way (the last one on the list, but not the last one by value) is simply to honestly implement all the necessary methods without putting this matter in the dark :-)

    Also popular now: