2012-10-31 48 views
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 . . . 

什麼?爲什麼所有那些殘害正常變量列表occurr?

回答

3

在專注於構建/破壞/複製(以及最終優化)的動態之前,有一個您似乎沒有意識到的問題:值不是多態

如果BA派生,

B b; 
A a(b); 

不會讓ab副本。它只會複製abA子組件。

不同的價值觀,指針和引用是多態

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

實際上將導致PA指向B的子組件,但pbpb2指向同一b

這就是說,一個vector<A>包含values,因此

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

將導致:

  • 創建一個空v;
  • 創建一個臨時B();
  • 使v大到足以包含A
  • 在v.end()創建一個從臨時B的A子組件複製的A.
  • 銷燬臨時乙

及 - 功能的結束時,

  • 破壞V(並因此完全破壞A在其內部)

存儲器是現在清潔。

如果使用指針:

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

將導致:

  • 創建一個空v
  • 在堆上創建乙
  • 放大v到包含A *
  • 將B的地址轉換爲其A的子組件地址(對於單一繼承,它們將最可能是相同的)
  • 在v.end()創建一個從B的轉換後的地址複製的A *(注意你正在轉換指針,而不是對象)。
  • 毀壞v
  • 銷燬其中的A *。
  • 堆上的B是仍然存在(內存泄漏,因爲沒有其他的方式來訪問它來刪除它)

爲了避免泄露你應該:

  • 創建乙在堆棧中,並得到它的地址或...
  • 在向量中使用std::unique_ptr<A>而不是A*(以便在向量銷燬時,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 
+0

我相信這是最好的答案,當你在頂部說明這些值不是多態的,而且它只是複製結構使它看起來如此,你對每種類型的矢量所經歷的過程的解釋都很容易理解並且相當徹底。 – FatalCatharsis

1

所有這些殘害發生在普通變量列表,因爲

list.push_back(B()); 

將分配矢量內一個新的對象,並使用賦值運算符的一個拷貝參數(見Does std::vector use the assignment operator of its value type to push_back elements?)。你用作參數的是臨時的,所以在創建後會被銷燬。

此外,破壞類型爲CB的對象將輸出兩行。在B的情況下,這將是

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

當你通過指針它使指針的值的副本,指向的對象沒有被修改。

就我個人而言,如果複製構造函數和賦值運算符是輕量級的並聲明爲inline,我認爲使用值向量的開銷可以忽略不計。

+0

不,這是不確定的行爲。 –

+0

沒有發現非虛擬析構函數... – UmNyobe

+0

除了與非虛擬析構函數發生的'事故',這個答案肯定是對的。但我認爲它應該提到另外兩件事情:a)在矢量內部進行重新分配和複製,b)矢量內的對象都是「A」類型。這對調用什麼析構函數有影響(甚至在虛擬化之後)。 – jogojapan

0

有幾件事情:

  1. 您使用的是物體的向量,所有的人,不是指針的話,你不能刪除矢量項目。好辦法可以讓這個Ÿ聲明指針這樣std::vector<A *>的載體,並與v.push_back(new B());v.push_back(new C());
  2. 的push_back他們,如果你加入到這個指針矢量A subclases,像B或C,你必須確保刪除所有「信息」。如果您沒有在A中聲明虛擬析構函數,則只會刪除子類信息,而不是基類信息。請記住所有繼承情況。

此外,請記住所有來自UmNyobe和Luchian的推薦。