Effective C++ノートの11:対象を複製する時そのすべての成分を忘れないでください


オブジェクト向けシステム(OO-systems)を設計すると、オブジェクトの内部がカプセル化され、オブジェクトのコピー(コピー)を担当する2つの関数しか残っていません.それは、適切な名前のcopy構造関数とcopy assignmentオペレータで、copying関数と呼ばれています.コンパイラは、必要に応じてclassesのcopying関数を作成し、コピーされたオブジェクトのすべてのメンバー変数をコピーする「コンパイラ生成版」の動作を説明します.
自分のcopying関数を宣言すると、デフォルトの実装のいくつかの動作が好きではないことをコンパイラに伝えることを意味します.コンパイラは犯されたように、奇妙な方法で敬意を表します.実装コードがほとんど間違いを犯すと、教えてくれません.
コンパイラによって作成されるのではなく、手動でcopying関数を書き出すclassを考慮して、外部からの呼び出しがCloggedに記録されるようにします.
void logCall(const std::string& funcName);//     log entry  
class Customer {  
public:  
    ......  
    Customer(const Customer& rhs);  
    Customer& operator=(const Customer& rhs);  
    ......  
private:  
    std::string name;  
};  
Customer::Customer(const Customer& rhs):name(rhs.name)//   rhs      
{  
    logCall("Customer copy constructor");  
}  
  
Customer& Customer::operator=(const Customer& rhs)  
{  
    logCall("Customer copy assignment operator");  
    name = rhs.name; //   rhs      
    return *this;    
}  

ここのすべてのことはよく見えますが、実際にはすべてのことも確かによく、別のメンバー変数が戦局に参加するまで:
class Date { ... };//     
class Customer {  
public:  
    ......         //     
private:  
    std::string name;  
    Date lastTransaction;  
};  

このとき既存のcopying関数はローカルコピー(partial copy):顧客のnameをコピーしたが、新しく追加したlastTransactionはコピーしなかった.ほとんどのコンパイラは、最高警告レベルでも文句を言わない.これはコンパイラが「自分でcopying関数を書く」ことに対する復讐行為です.copying関数を書くことを拒否した以上、コードが不完全であれば、教えてくれません.結論:classにメンバー変数を追加する場合は、copying関数を同時に変更する必要があります.(classのすべてのコンストラクション関数と非標準形式のoperator=を変更する必要があります.忘れた場合は、コンパイラが注意することはできません.)
継承が発生すると、このテーマが最も暗躍する潜在的な危機をもたらす可能性がある.検討:
class PriorityCustomer: public Customer {//   derivedclass  
public:  
    ......  
    PriorityCustomer(const PriorityCustomer& rhs);  
    PriorityCustomer& operator=(const PriorityCustomer& rhs);  
    ......  
private:  
    int priority;  
};  
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)  
: priority(rhs.priority)  
{  
    logCall("PriorityCustomer copy constructor");  
}  
PriorityCustomer&  
PriorityCustomer::operator=(const PriorityCustomer& rhs)  
{  
    logCall("PriorityCustomer copy assignment operator");  
    priority = rhs.priority;  
    return *this;  
}  

PriorityCustomerのcopying関数は、PriorityCustomer内のすべてのものをコピーしているように見えますが、もう一度見てください.はい、PriorityCustomerによって宣言されたメンバー変数がコピーされますが、各PriorityCustomerには継承されたCustomerメンバー変数のコピーが含まれていますが、メンバー変数はコピーされていません.PriorityCustomerのcopyコンストラクション関数は、実パラメータがbase classコンストラクション関数に渡されることを指定していません(つまり、メンバーの初期値列(member initialization list)にはCustomerは記載されていません).したがって、PriorityCustomerオブジェクトのCustomerコンポーネントは、実パラメータを持たないCustomerコンストラクション関数(すなわちdefaultコンストラクション関数は必ず1つあります.そうしないとコンパイルできません)によって初期化されます.defaultコンストラクション関数は、nameとlastTransactionに対してデフォルトの初期化動作を実行します.
「derived classのcopying関数を書く」という重責を負う限り、base class成分も注意深くコピーする必要があります.それらの成分はprivateであることが多いので、直接アクセスすることはできません.derived classのcopying関数に対応するbase class関数を呼び出す必要があります.
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)  
: Customer (rhs) , //   base class copy      
  priority(rhs.priority)  
{  
    logCall("PriorityCustomer copy constructor");  
}  
PriorityCustomer&  
PriorityCustomer::operator=(const PriorityCustomer& rhs)  
{  
    logCall("PriorityCustomer copy assignment operator");  
    Customer::operator=(rhs); //  base class          
    priority = rhs.priority;  
    return *this;  
}  

この条項のタイトルでいう「各成分をコピーする」ことは、今では明らかになっているはずです.copying関数を作成する場合は、(1)すべてのlocalメンバー変数をコピーし、(2)すべてのbase classes内の適切なcopying関数を呼び出すことを確認します.
この2つのcopying関数には、コードの重複を避けるために別の関数を呼び出すように誘導される可能性があります.このような精進的な態度は賞賛に値するが、あるcopying関数に別のcopying関数を呼び出すと、あなたが望む目標を達成できない.copy assignmentオペレータにcopyコンストラクション関数を呼び出すのは、既存のオブジェクトを構築しようとしているように不合理です.このことはこんなにでたらめで,甚だしきに至っては関連文法がまったくない.あなたが望んでいるように見える文法がありますが、実はそうではありません.確かに文法の背後には本当にそれをしたものもありますが、場合によってはあなたの対象を壊してしまうことがあります.だから、私はそれらの文法をあなたに見せるつもりはありません.単純にこの記述を受け入れましょう.copy assignmentオペレータにcopy構造関数を呼び出すべきではありません.逆方向に1つずつcopyコンストラクション関数にcopy assignmentオペレータを呼び出すのは同じように意味がありません.コンストラクション関数は新しいオブジェクトを初期化するために使用されますが、copy assignmentオペレータは自己初期化オブジェクトにのみ実行されます.まだ構築されていないオブジェクトに値を割り当てるのは、まだ初期化されていないオブジェクトに「自分の初期化オブジェクトにのみ意味がある」ことをするのと同じです.つまらないですね.やってみるな.copyコンストラクション関数とcopy assignmentオペレータに近いコードがあることを発見したら、重複コードを除去する方法は、→新しいメンバー関数を作成して両者に呼び出すことです.このような関数はprivateであり、initと命名されることが多い.このポリシーは、copyコンストラクション関数とcopy assignmentオペレータ間のコード重複を安全に除去することができる.
覚えておく必要がある
1.Copying関数は、「オブジェクト内のすべてのメンバー変数」および「すべてのbase class成分」がコピーされていることを確認します.2.あるcopying関数で別のcopying関数を実装しないでください.共通機能は3番目の関数に格納され、2つのcoping関数によって共通に呼び出されます.