6

次のコードを検討してください。

#include <vector>
#include <iostream>
using namespace std;

class Base
{
    char _type;
public:
    Base(char type):
        _type(type)
    {}

    ~Base() {
        cout << "Base destructor: " << _type << endl;
    }
};

class uncopyable
{
    protected:
        uncopyable() {}
        ~uncopyable() {}
    private:
        uncopyable( const uncopyable& );
        const uncopyable& operator=( const uncopyable& );
};

class Child : public Base, private uncopyable
{
    int j;
public:
    Child():
        Base('c')
    {}
    ~Child() {
        cout << "Child destructor" << endl;
    }
};


int main()
{
    vector<Base> v;
    Base b('b');
    Child c;

    v.push_back(b);
    v.push_back(c);
    return 0;
}

私のシステムの出力は次のとおりです。

Base destructor: b
Child destructor
Base destructor: c
Base destructor: b
Base destructor: b
Base destructor: c

私の質問は次のとおりです。

  • (型 bの) のデストラクタがBase2 回ではなく 3 回呼び出されるのはなぜですか (オブジェクト b のコピーが 2 つ以上あるのでしょうか)。

  • Child親の 1 つのコピー コンストラクターがプライベートであることを考慮して、type のオブジェクトをコピーするとどうなりますか。未定義の動作ですか?

  • type のオブジェクトをコピーしようとすると、コンパイル時にエラーが発生することを期待していましたChild。子のデフォルトのコピー コンストラクターが Uncopyable クラスのプライベート コピー コンストラクターを呼び出そうとすると、コンパイル エラーが発生すると思いました。コンパイルエラーが発生しないのはなぜですか?

コードがこのように設計されている理由は、Childクラスが巨大だからです。

Child望ましい動作は、クライアントがオブジェクトをコピーしようとするたびに子データを破棄することです ( のデストラクタChildを呼び出さずに のデストラクタを呼び出しBaseます)。

このコードはそれを実現しますが、未定義の動作が発生し、メモリリークが発生すると思います (Childコピーされたインスタンスのデストラクタを呼び出すことはありません)。

4

3 に答える 3

9

コードで何が起こるかを次に示します。

int main() 
{ 
    vector<Base> v;    // 1
    Base b('b');       // 2
    Child c;           // 3

    v.push_back(b);    // 4
    v.push_back(c);    // 5
    return 0; 
}                      // 6
  1. 1行目:構築されたベクトルv

  2. 2行目:ベースbが構築されました(ベースのコンストラクターを呼び出します)

  3. 3行目:構築された子c(子のコンストラクターとベースのコンストラクターを呼び出す)

  4. 4行目:vは現在最大容量であり、サイズを変更する必要があります。
    メモリはvによってBaseの1つの要素に割り当てられます。Basebは
    v[0]にコピーされます(Baseのコピーコンストラクターを呼び出します)。

  5. 5行目:vは再び最大容量になり、サイズを変更する必要があります。
    メモリはvによってBaseの2つの要素に割り当てられます。
    古いv[0]は新しいv[0]にコピーされます(Baseのコピーコンストラクターを呼び出します)。
    古いv[0]が削除されます(Baseのデストラクタ( "Baseデストラクタ:b")を呼び出します)。
    子cはv[1]にコピーされます(Baseのコピーコンストラクターを呼び出します)。

  6. 6行目:c、b、およびvがスコープ外になっています。
    子cが削除されます(子のデストラクタ( "子デストラクタ")を呼び出し、次にベースのデストラクタ( "ベースデストラクタ:c")。
    ベースbが削除されます(ベースのデストラクタ( "ベースデストラクタ:b")を呼び出します)。
    ベースv [0]、 v [1]が削除されます(ベースのデストラクタを2回呼び出します(「ベースデストラクタ:b」、「ベースデストラクタ:c」))。

メモリリークはありません。上記のシーケンスのすべてのコンストラクタに対して、対応するデストラクタが呼び出されます。

さらに、コピーコンストラクタについて非常に混乱しているようです。子cはBase&としてpush_backに渡され、期待どおりにBaseのコピーコンストラクターが呼び出されます。Baseの暗黙的なコピーコンストラクターは仮想またはオーバーライドされていないため、子をコピー不可から派生させても、これを変更することはできません。

vector<Base>は、Child型のオブジェクトを格納できないことに注意してください。ベースに十分なメモリを割り当てることだけを知っています。子のインスタンスをベースに割り当てるときに発生することは、スライスと呼ばれます。これは、意図せず誤解されることがよくありますが、説明したシナリオで実際に必要なもののように見えます。

于 2012-05-19T02:10:45.470 に答える
3

Child 型のオブジェクトをコピーしようとすると、コンパイル時にエラーが発生することが予想されていました。

Childオブジェクトをコピーしていません。に入れるChild cvector<Base>、 のみBaseがコピーされます。基本的には実行と同じb = c;です。コピー/割り当てChildを行うと、エラーが発生します。

Child d = c;  // compile error

既定のコピー コンストラクターは、基本クラスとメンバー オブジェクトのコピー コンストラクターを呼び出し、プリミティブとポインターのビットごとのコピーを行います。

于 2012-05-19T01:21:29.870 に答える
0

編集:答えは間違っています..現在、より良い応答に編集しています。

Base (型 b) のデストラクタが 2 回ではなく 3 回呼び出されるのはなぜですか (オブジェクト b のコピーが 2 つ以上あるのでしょうか)。

最も可能性が高いのは、Vector が のコピーを作成していることですb。ベクトルはそれを頻繁に行います。

親の 1 つのコピー コンストラクターがプライベートであることを考慮して、型 Child のオブジェクトをコピーするとどうなるでしょうか。未定義の動作ですか?

いいえ。C のコピー コンストラクターは、基本クラスのコピー コンストラクターを呼び出します。したがって、基本クラスのコピー コンストラクターがプライベートの場合、コンパイルされません。

Base クラス オブジェクトのコピーを許可しているときに Child 型のオブジェクトをコピーしようとすると、常にコンパイル エラーが発生する必要があります。それを行う最善の方法は何ですか?

次のように、Child のプライベート コピー コンストラクターを宣言します。

private:
    Child(const Child& a) {
        throw "cannot make a copy";
    } 

望ましい動作は、クライアントが Child オブジェクトをコピーしようとするたびに子データを破棄することです (Base のデストラクタを呼び出さずに Child のデストラクタを呼び出します)。

よく分からない。コピー コンストラクターは、新しいオブジェクトを作成することを意味します。(古い) オブジェクトに対して操作を行うことはできません。

于 2012-05-19T00:51:50.333 に答える