复制具有用户定义的类成员的类的构造函数

时间:2013-02-24 17:11:54

标签: c++ copy-constructor composition assignment-operator

我正在阅读c ++第14章中的思考:“不自动继承的函数”

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

在上面的代码中,我对Game类的复制构造函数和赋值构造函数感到困惑:

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

作者给出了评论:“ 您必须显式调用GameBoard复制构造函数,否则会自动调用默认构造函数: ”为什么如果我不显式调用GameBoard拷贝构造函数,然后将调用默认构造函数?

对于赋值构造函数, 如果我没有显式调用GameBoard赋值运算符,则不会发生赋值。 为什么?

3 个答案:

答案 0 :(得分:4)

  

为什么如果我没有显式调用GameBoard拷贝构造函数,那么将调用默认构造函数?

如果您没有为Game编写任何明确的复制构造函数,那么编译器会为您生成一个复制构造gb的复制构造函数。另一方面,在您明确定义自己的复制构造函数的那一刻,编译器认为“好吧,这家伙真的知道他在做什么,所以让我们给他完全控制

通过获得完全控制权,您还有责任明确指定在复制构建期间要执行的所有操作。如果未指定任何操作,则编译器必须假定应采用构造对象的默认机制。构造对象的默认机制是调用默认构造函数

由于您似乎知道自己在做什么,因此编译器不会对成员变量的正确构造过程做出任何假设。你想要完全控制,现在你拥有它:忘了一些东西?你得到默认结构。

  

如果我没有显式调用GameBoard赋值运算符,则不会发生赋值。为什么呢?

答案非常相似。通过显式定义自己的operator =,您告诉编译器您希望完全控制对象的分配方式。编译器不会试图通过做出可能不正确的假设来开始。相反,它只会放弃并留下你所有的决定权。

另一方面,假设编译器以静默方式生成赋值gb = g.gb,但是对于某些(希望是好的)原因,希望执行该赋值:你将如何告诉编译器执行它,如果默认行为是生成这样的指令吗?

此外,除非确实需要,否则让编译器为您的类隐式生成复制/移动构造函数,析构函数和复制/移动赋值运算符是一种很好的编程习惯:for更多信息,请参阅R. Martinho Fernandes关于所谓 Rule of Zero的文章。

希望这有助于澄清。

答案 1 :(得分:0)

这些评论对我来说没有意义。

如果您将代码简化为以下内容:

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

你会(或者应该)得到这样的输出:

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()

也许他正在谈论这样一个事实:当您定义自己的构造函数/赋值运算符时,默认情况下默认情况下会发生 not 。如果是这样,那是真的(也许我们因为同样的原因而感到困惑:因为这似乎是非常明显的事情)。

答案 2 :(得分:0)

自定义复制构造函数和/或赋值运算符时,必须处理所有成员变量。

如果不对复制构造函数和/或赋值运算符进行编码,则编译器会生成默认值。请注意,默认值执行浅层分配和复制。两者都只是遍历每个成员变量并使用它们的赋值运算符或复制构造函数。

// 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.
}