4

変数のベクトルを使用する場合と、動的メモリを使用するポインターのベクトルを使用する場合の違いに興味がありましたが、混乱するものを見つけました。次のような単純な main.cpp があります。

#include <iostream>
#include <vector>

using namespace std;

class A
{
public:
    A() { x = 2;}
    virtual ~A() { cout << "I'm a dead A\n";}

public:
    int x;
};

class B : public A
{
public:
    B() {x = 4;}
    ~B() { cout << "I'm a dead B\n";}
};

class C : public A
{
public:
    C() { x = 6;}
    ~C() { cout << "I'm a dead C\n";}
};

int main()
{
    cout << "Starting variable list\n";
    std::vector<A> list;

    list.push_back( B() );
    list.push_back( A() );
    list.push_back( B() );
    list.push_back( C() );
    list.push_back( A() );


    for(std::vector<A>::iterator it = list.begin(); it != list.end(); it++)
    {
        cout << it->x << endl;
    }

    cout << "\n\nStarting pointer list\n";

    std::vector<A *> ptrList;

    ptrList.push_back( new B());
    ptrList.push_back( new A());
    ptrList.push_back( new B());
    ptrList.push_back( new C());
    ptrList.push_back( new A());

    for(std::vector<A *>::iterator it = ptrList.begin(); it != ptrList.end(); it++)
    {
        cout << (*it)->x << endl;
    }

    for(std::vector<A *>::iterator it = ptrList.begin(); it != ptrList.end(); it++)
    {
        delete *it;
    }

    system("PAUSE");
    return 0;
}

そして、次のような印刷物が得られます。

Starting variable list
I'm a dead B
I'm a dead A
I'm a dead A
I'm a dead A
I'm a dead A
I'm a dead A
I'm a dead B
I'm a dead A
I'm a dead A
I'm a dead A
I'm a dead A
I'm a dead C
I'm a dead A
I'm a dead A
I'm a dead A
I'm a dead A
I'm a dead A
I'm a dead A
4
2
4
6
2


Starting pointer list
4
2
4
6
2
I'm a dead B
I'm a dead A
I'm a dead A
I'm a dead B
I'm a dead A
I'm a dead C
I'm a dead A
I'm a dead A
Press any key to continue . . .

これらすべての破壊は、通常の変数リストで何が、なぜ発生したのでしょうか?

4

3 に答える 3

3

構築/破壊/コピー (および最終的な最適化) のダイナミクスに集中する前に、あなたが気づいていないように見える考慮事項があります:値は多態的ではありません

Bから派生する場合A

B b;
A a(b);

aのコピーを作成しませんbaのサブコンポーネントにコピーするだけbですA

値とは異なり、ポインターと参照は多態的です。

B b;
B* pb = &b;
A* pa = pb;
B* pb2 = const_cast<B*>(pa);

実際には、pa は b の A サブコンポーネントを指しますが、pbandpb2は同じ を指しbます。

つまり、 avector<A>には A が含まれているvaluesため、

vecotr<A> v;
v.push_back(B());

結果は次のとおりです。

  • 空のv;を作成します。
  • 一時的な B(); を作成します。
  • v を A を含むのに十分な大きさにする
  • v.end() で、一時的な B の A サブコンポーネントからコピーされた A を作成します。
  • 一時Bを破壊する

そして - 関数の最後に、

  • v を破棄する (したがって、その中の A を破棄する)

これでメモリがクリーンになりました。

ポインターを使用する場合:

vector<A*> v;
v.push_back(new B());

結果は次のとおりです。

  • 空の作成v
  • ヒープに B を作成する
  • A* を含むように v を拡大します
  • B のアドレスを A のサブコンポーネント アドレスに変換します (単一継承の場合、それらは同じである可能性が高くなります)。
  • v.end() で、B の変換されたアドレスからコピーされた A* を作成します (オブジェクトではなくポインターを変換していることに注意してください)。
  • vを破壊する
  • その中の A* を破壊します。
  • ヒープ上の B はまだそこにあります (削除するためにアクセスする方法が他にないため、メモリ リーク)

リークを回避するには、次のいずれかを行う必要があります。

  • スタック上に B を作成し、そのアドレスを取得するか...
  • std::unique_ptr<A>vector の代わりにa を使用しますA*(そのため、vector の破棄時に unique_ptr が破棄され、そのデストラクタが指定された A サブオブジェクトを破棄します。つまり、仮想デストラクタを使用すると、B が破棄されます。

上記の問題に関するより効果的なデモンストレーションは、次のコードで行うことができます。

// Compile as g++ -pedantic -Wall -std=c++11

#include <vector>
#include <list>
#include <iostream>

class A
{
public:
    A() { std::cout << "- creating A at " << this << std::endl; }
    A(const A& a) { std::cout << "- creating A at " << this << " from " << &a << std::endl; }
    A& operator=(const A& a) { std::cout << "- assigning A at " << this << " from " << &a << std::endl; return *this; }
    virtual ~A() { std::cout << "- destroying A at " << this << std::endl; }
    virtual void hello() const { std::cout << "- A's hello from " << this << std::endl; }
};

class B: public A
{
public:
    B() { std::cout << "- creating B at " << this << std::endl; }
    B(const B& a) { std::cout << "- creating B at " << this << " from " << &a << std::endl; }
    B& operator=(const B& a) { std::cout << "- assigning B at " << this << " from " << &a << std::endl; return *this; }
    virtual ~B() { std::cout << "- destroying B at " << this << std::endl; }
    virtual void hello() const { std::cout << "- B's hello from " << this << std::endl; }
};

class C: public A
{
public:
    C() { std::cout << "- creating C at " << this << std::endl; }
    C(const C& a) { std::cout << "- creating C at " << this << " from " << &a << std::endl; }
    C& operator=(const C& a) { std::cout << "- assigning C at " << this << " from " << &a << std::endl; return *this; }
    virtual ~C() { std::cout << "- destroying C at " << this << std::endl; }
    virtual void hello() const { std::cout << "- C's hello from " << this << std::endl; }
};

int main()
{
    std::cout << "creating some objects" << std::endl;
    A a1, a2;
    B b1, b2;
    C c1, c2;

    {
        std::cout << "operating with values" << std::endl;
        std::vector<A> valvect;
        valvect.push_back(a1);
        valvect.push_back(a1);
        valvect.push_back(b1);
        valvect.push_back(b1);
        valvect.push_back(c1);
        valvect.push_back(c1);
        valvect.push_back(a2);
        valvect.push_back(a2);
        valvect.push_back(b2);
        valvect.push_back(b2);
        valvect.push_back(c2);
        valvect.push_back(c2);
        for(const auto& x: valvect) x.hello();
        std::cout << "at '}' destroy the value vector" << std::endl;
    }


    {
        std::cout << "operating with pointers" << std::endl;
        std::vector<A*> ptrvect;
        ptrvect.push_back(&a1);
        ptrvect.push_back(&a1);
        ptrvect.push_back(&b1);
        ptrvect.push_back(&b1);
        ptrvect.push_back(&c1);
        ptrvect.push_back(&c1);
        ptrvect.push_back(&a2);
        ptrvect.push_back(&a2);
        ptrvect.push_back(&b2);
        ptrvect.push_back(&b2);
        ptrvect.push_back(&c2);
        ptrvect.push_back(&c2);
        for(const auto& x: ptrvect)
            x->hello();
        std::cout << "at '}' destroy the pointer's vector" << std::endl;
    }

    {
        std::cout << "operating with list of values" << std::endl;
        std::list<A> vallst;
        vallst.push_back(a1);
        vallst.push_back(a1);
        vallst.push_back(b1);
        vallst.push_back(b1);
        vallst.push_back(c1);
        vallst.push_back(c1);
        vallst.push_back(a2);
        vallst.push_back(a2);
        vallst.push_back(b2);
        vallst.push_back(b2);
        vallst.push_back(c2);
        vallst.push_back(c2);
        for(const auto& x: vallst)
            x.hello();
        std::cout << "at '}' destroy the value list" << std::endl;
    }


    {
        std::cout << "operating with list of pointers" << std::endl;
        std::list<A*> ptrlst;
        ptrlst.push_back(&a1);
        ptrlst.push_back(&a1);
        ptrlst.push_back(&b1);
        ptrlst.push_back(&b1);
        ptrlst.push_back(&c1);
        ptrlst.push_back(&c1);
        ptrlst.push_back(&a2);
        ptrlst.push_back(&a2);
        ptrlst.push_back(&b2);
        ptrlst.push_back(&b2);
        ptrlst.push_back(&c2);
        ptrlst.push_back(&c2);
        for(const auto& x: ptrlst)
            x->hello();
        std::cout << "at '}' destroy the pointer's list" << std::endl;
    }



    std::cout << "now finally at '};' destroy the objects created at the beginning" << std::endl;
    return 0;
}

のように出力されます。

creating some objects
- creating A at 0x22febc
- creating A at 0x22feb8
- creating A at 0x22feb4
- creating B at 0x22feb4
- creating A at 0x22feb0
- creating B at 0x22feb0
- creating A at 0x22feac
- creating C at 0x22feac
- creating A at 0x22fea8
- creating C at 0x22fea8
operating with values
- creating A at 0x3e3eb8 from 0x22febc
- creating A at 0x3e2434 from 0x22febc
- creating A at 0x3e2430 from 0x3e3eb8
- destroying A at 0x3e3eb8
- creating A at 0x3e2448 from 0x22feb4
- creating A at 0x3e2440 from 0x3e2430
- creating A at 0x3e2444 from 0x3e2434
- destroying A at 0x3e2430
- destroying A at 0x3e2434
- creating A at 0x3e244c from 0x22feb4
- creating A at 0x3e2468 from 0x22feac
- creating A at 0x3e2458 from 0x3e2440
- creating A at 0x3e245c from 0x3e2444
- creating A at 0x3e2460 from 0x3e2448
- creating A at 0x3e2464 from 0x3e244c
- destroying A at 0x3e2440
- destroying A at 0x3e2444
- destroying A at 0x3e2448
- destroying A at 0x3e244c
- creating A at 0x3e246c from 0x22feac
- creating A at 0x3e2470 from 0x22feb8
- creating A at 0x3e2474 from 0x22feb8
- creating A at 0x3e24a0 from 0x22feb0
- creating A at 0x3e2480 from 0x3e2458
- creating A at 0x3e2484 from 0x3e245c
- creating A at 0x3e2488 from 0x3e2460
- creating A at 0x3e248c from 0x3e2464
- creating A at 0x3e2490 from 0x3e2468
- creating A at 0x3e2494 from 0x3e246c
- creating A at 0x3e2498 from 0x3e2470
- creating A at 0x3e249c from 0x3e2474
- destroying A at 0x3e2458
- destroying A at 0x3e245c
- destroying A at 0x3e2460
- destroying A at 0x3e2464
- destroying A at 0x3e2468
- destroying A at 0x3e246c
- destroying A at 0x3e2470
- destroying A at 0x3e2474
- creating A at 0x3e24a4 from 0x22feb0
- creating A at 0x3e24a8 from 0x22fea8
- creating A at 0x3e24ac from 0x22fea8
- A's hello from 0x3e2480
- A's hello from 0x3e2484
- A's hello from 0x3e2488
- A's hello from 0x3e248c
- A's hello from 0x3e2490
- A's hello from 0x3e2494
- A's hello from 0x3e2498
- A's hello from 0x3e249c
- A's hello from 0x3e24a0
- A's hello from 0x3e24a4
- A's hello from 0x3e24a8
- A's hello from 0x3e24ac
at '}' destroy the value vector
- destroying A at 0x3e2480
- destroying A at 0x3e2484
- destroying A at 0x3e2488
- destroying A at 0x3e248c
- destroying A at 0x3e2490
- destroying A at 0x3e2494
- destroying A at 0x3e2498
- destroying A at 0x3e249c
- destroying A at 0x3e24a0
- destroying A at 0x3e24a4
- destroying A at 0x3e24a8
- destroying A at 0x3e24ac
operating with pointers
- A's hello from 0x22febc
- A's hello from 0x22febc
- B's hello from 0x22feb4
- B's hello from 0x22feb4
- C's hello from 0x22feac
- C's hello from 0x22feac
- A's hello from 0x22feb8
- A's hello from 0x22feb8
- B's hello from 0x22feb0
- B's hello from 0x22feb0
- C's hello from 0x22fea8
- C's hello from 0x22fea8
at '}' destroy the pointer's vector
operating with list of values
- creating A at 0x3e2448 from 0x22febc
- creating A at 0x3e24d0 from 0x22febc
- creating A at 0x3e24e8 from 0x22feb4
- creating A at 0x3e2500 from 0x22feb4
- creating A at 0x3e2518 from 0x22feac
- creating A at 0x3e2530 from 0x22feac
- creating A at 0x3e2548 from 0x22feb8
- creating A at 0x3e2560 from 0x22feb8
- creating A at 0x3e2578 from 0x22feb0
- creating A at 0x3e2590 from 0x22feb0
- creating A at 0x3e25a8 from 0x22fea8
- creating A at 0x3e25c0 from 0x22fea8
- A's hello from 0x3e2448
- A's hello from 0x3e24d0
- A's hello from 0x3e24e8
- A's hello from 0x3e2500
- A's hello from 0x3e2518
- A's hello from 0x3e2530
- A's hello from 0x3e2548
- A's hello from 0x3e2560
- A's hello from 0x3e2578
- A's hello from 0x3e2590
- A's hello from 0x3e25a8
- A's hello from 0x3e25c0
at '}' destroy the value list
- destroying A at 0x3e2448
- destroying A at 0x3e24d0
- destroying A at 0x3e24e8
- destroying A at 0x3e2500
- destroying A at 0x3e2518
- destroying A at 0x3e2530
- destroying A at 0x3e2548
- destroying A at 0x3e2560
- destroying A at 0x3e2578
- destroying A at 0x3e2590
- destroying A at 0x3e25a8
- destroying A at 0x3e25c0
operating with list of pointers
- A's hello from 0x22febc
- A's hello from 0x22febc
- B's hello from 0x22feb4
- B's hello from 0x22feb4
- C's hello from 0x22feac
- C's hello from 0x22feac
- A's hello from 0x22feb8
- A's hello from 0x22feb8
- B's hello from 0x22feb0
- B's hello from 0x22feb0
- C's hello from 0x22fea8
- C's hello from 0x22fea8
at '}' destroy the pointer's list
now finally at '};' destroy the objects created at the beginning
- destroying C at 0x22fea8
- destroying A at 0x22fea8
- destroying C at 0x22feac
- destroying A at 0x22feac
- destroying B at 0x22feb0
- destroying A at 0x22feb0
- destroying B at 0x22feb4
- destroying A at 0x22feb4
- destroying A at 0x22feb8
- destroying A at 0x22febc
于 2012-10-31T10:14:40.533 に答える
1

これらの破壊はすべて通常の変数リストで発生しました。

    list.push_back( B() );

ベクトル内に新しいオブジェクトを割り当て、代入演算子を使用して引数のオブジェクトをコピーします ( Does std::vector use the assignment operator of its value type to push_back elements? を参照してください)。引数として使用したものは一時的なものなので、作成後に破棄されます。

Cさらに、タイプorのオブジェクトを破棄すると、B2 行が出力されます。その場合B

I'm a dead B
I'm a dead A

ポインターを渡すと、ポインターの値のコピーが作成されます。ポイントされているオブジェクトは変更されません。

個人的には、コピー コンストラクターと代入演算子が軽量で宣言されている場合、値のベクトルを使用するオーバーヘッドは無視できると思いますinline

于 2012-10-31T08:43:41.630 に答える
0

いくつかのこと:

  1. A オブジェクトのベクトルを使用していますが、それらはすべてポインターではなく、ベクトル項目を削除することはできません。この y が A ポインターのベクトルを宣言できるようにし、 or でそれらを push_back できるようにする良いstd::vector<A *>方法v.push_back(new B());v.push_back(new C());
  2. このポインター ベクトルに A サブクラス (B や C など) を追加する場合は、すべての「情報」を確実に削除する必要があります。で仮想デストラクタを宣言しない場合はA、サブクラスの情報のみが削除され、基本クラスの情報は削除されません。すべての継承状況でこれを覚えておいてください。

さらに、UmNyobe と Luchian からのすべての推奨事項を念頭に置いてください。

于 2012-10-31T08:57:28.217 に答える