简体   繁体   中英

copy constructor of a class which has an user-defined class member

I'm reading thinking in c++ chapter 14 : "Functions that don't automatically inherit"

class GameBoard {
public:
  GameBoard() { cout << "GameBoard()\n"; }
  GameBoard(const GameBoard&) { 
    cout << "GameBoard(const GameBoard&)\n"; 
  }
  GameBoard& operator=(const GameBoard&) {
    cout << "GameBoard::operator=()\n";
    return *this;
  }
  ~GameBoard() { cout << "~GameBoard()\n"; }
};

class Game {
  GameBoard gb; // Composition
public:
  // Default GameBoard constructor called:
  Game() { cout << "Game()\n"; }
  // You must explicitly call the GameBoard
  // copy-constructor or the default constructor
  // is automatically called instead:
  Game(const Game& g) : gb(g.gb) { 
  //Game(const Game& g) {
    cout << "Game(const Game&)\n"; 
  }
  Game(int) { cout << "Game(int)\n"; }
  Game& operator=(const Game& g) {
    // You must explicitly call the GameBoard
    // assignment operator or no assignment at 
    // all happens for gb!
    gb = g.gb;
    cout << "Game::operator=()\n";
    return *this;
  }
  class Other {}; // Nested class
  // Automatic type conversion:
  operator Other() const {
    cout << "Game::operator Other()\n";
    return Other();
  }
  ~Game() { cout << "~Game()\n"; }
};

In the above code, I'm confused by the copy constructor and assignment constructor of Game class:

      // You must explicitly call the GameBoard
      // copy-constructor or the default constructor
      // is automatically called instead:
      Game(const Game& g) : gb(g.gb) { 
      //Game(const Game& g) {
        cout << "Game(const Game&)\n"; 
      }

      Game& operator=(const Game& g) {
        // You must explicitly call the GameBoard
        // assignment operator or no assignment at 
        // all happens for gb!
        gb = g.gb;
        cout << "Game::operator=()\n";
        return *this;
      }

The author gives the comment: " You must explicitly call the GameBoard copy-constructor or the default constructor is automatically called instead: " Why if I don't explicitly call the GameBoard copy-constructor, then the default constructor will be called?

And for the assignment constructor, if I don't explicitly call the GameBoard assignment operator, then no assignment happens. Why?

Why if I don't explicitly call the GameBoard copy-constructor, then the default constructor will be called?

If you hadn't written any explicit copy-constructor for Game , then the compiler would generate one for you that copy-constructs gb . On the other hand, in the moment you explicitly define your own copy constructor, the compiler thinks "Ok, this guy really knows what he's doing, so let's give him full control " .

By gaining full control, you also have the responsibility of explicitly specifying exactly all the actions that you want to be taken during your copy construction. If you do not specify any actions, then the compiler must assume that the default mechanism for constructing objects shall be adopted. And the default mechanism for constructing objects is to invoke the default constructor .

Since you seem to know what you're doing, the compiler won't make any assumptions on the correct construction procedure for your member variables. You wanted full control, now you have it: forgot something? You get default construction.

if I don't explicitly call the GameBoard assignment operator, then no assignment happens. Why?

The answer is pretty similar. By explicitly defining your own operator = , you are telling the compiler that you want to take full control over the way your objects are assigned. The compiler won't try to get on your way by making assumptions that might not be correct. Instead, it will just set aside and leave you all the decision power.

On the other hand, suppose the compiler silently generated the assignment gb = g.gb , but for some (hopefully good) reason you didn't want that assignment to be performed: how would you tell the compiler not to perform it, if the default behavior were to generate such an instruction?

Besides, unless it is really needed, it is a good programming practice to let the compiler generate implicitly a copy/move constructor, destructor, and a copy/move assignment operator for your class: for more information, see this article by R. Martinho Fernandes on the so-called Rule of Zero .

Hope this helped clarifying.

These comments don't really make sense to me.

If you simplify the code to something like this:

#include <iostream>

using namespace std;

class GameBoard {
public:
  GameBoard() { cout << "GameBoard()\n"; }
  GameBoard(const GameBoard&) { 
    cout << "GameBoard(const GameBoard&)\n"; 
  }
  GameBoard& operator=(const GameBoard&) {
    cout << "GameBoard::operator=()\n";
    return *this;
  }
  ~GameBoard() { cout << "~GameBoard()\n"; }
};

class Game {
  GameBoard gb; // Composition
};

int main() { 
    cout << "Default Constructing Game object\n";
    Game g;

    cout << "\nCopy constructing Game object\n";
    Game h = g; // uses copy ctor

    cout << "\nDefault constructing another Game object\n";
    Game i;

    cout << "\nAssigning a Game object\n";
    i = g;  // uses assignment operator.
    return 0;
}

You will (or should, anyway) get output like this:

Default Constructing Game object
GameBoard()

Copy constructing Game object
GameBoard(const GameBoard&)

Default constructing another Game object
GameBoard()

Assigning a Game object
GameBoard::operator=()
~GameBoard()
~GameBoard()
~GameBoard()

Maybe he's talking about the fact that what happens by default will not happen by default when you define your own constructor/assignment operator. If so, that's true (and maybe we're confused for the same reason: because this seems to be belaboring the exceptionally obvious).

When you customize the copy constructor and or assignment operator, you have to handle all of your member variables.

When you do not code the copy constructor and/or assignment operator, default ones are generate by the compiler. Be aware that the defaults do shallow assignments and copies. Both just go through each member variables and use their assignment operators or copy constructors.

// NOTE don't you strdup or free in c++ (see new, delete and the string class)
// this is for BAD example only
class Stuff {
public:
   char * thing;
   Stuff( void ) : thing(0) {}
   ~Stuff() { if( thing ) free thing; };
   void copyThing( char * other ) { thing = strdup(other); }

}

class Other {
public:
    Stuff   myStuff;
}

void BadExample() {
    Other oa;
    oa.copyThing( "Junk" );
    Other ob;
    ob = oa; // will work but oa and ob myStuff.thing will point to the same address
             // and you will attempt to free it twice during deconstruction
}

class BetterStuff {
public:
   char * thing;
   BetterStuff( void ) : thing(0) {}
   ~BetterStuff() { if( thing ) free thing; };
   void copyThing( char * other ) { thing = strdup(other); }
   BetterStuff & operator = ( const BetterStuff & rhs ) {
     if( rhs.thing ) thing = strdup( rhs.thing );
   }
}


class BetterOther {
public:
    BetterStuff   myStuff;
}


void Example() {
    BetterOther oa;
    oa.copyThing( "Junk" );
    BetterOther ob;

    ob = oa; // now ob has a private copy of the string and can free it.
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM