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

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

本文关键字:成员 构造函数 复制 用户 定义      更新时间:2023-10-16

我正在阅读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赋值运算符,那么就不会发生赋值为什么?

为什么如果我没有显式调用GameBoard副本构造函数,那么会调用默认构造函数?

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

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

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

如果我没有明确调用GameBoard赋值操作符,那么就不会发生赋值。为什么?

答案很相似。通过显式定义自己的operator =,您告诉编译器您希望完全控制对象的分配方式。编译器不会试图通过做出可能不正确的假设来阻碍你。相反,它只会搁置一边,留给你所有的决定权。

另一方面,假设编译器无声地生成了赋值gb = g.gb,但出于某种(希望是好的)原因,您不希望执行该赋值:如果默认行为是生成这样的指令,您将如何告诉编译器不要执行它?

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

希望这有助于澄清。

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

如果你把代码简化为这样的东西:

#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 objectn";
    Game g;
    cout << "nCopy constructing Game objectn";
    Game h = g; // uses copy ctor
    cout << "nDefault constructing another Game objectn";
    Game i;
    cout << "nAssigning a Game objectn";
    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()

也许他说的是,当你定义自己的构造函数/赋值运算符时,默认情况下发生的事情将而不是。如果是这样的话,那就是真的(也许我们也因为同样的原因感到困惑:因为这似乎过于明显了)。

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

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

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