2010-06-07 35 views
1

這是我的代碼示例:安全放置新的和明確的析構函數調用

template <typename T> struct MyStruct { 
    T object; 
} 

template <typename T> class MyClass { 
    MyStruct<T>* structPool; 
    size_t structCount; 

    MyClass(size_t count) { 
     this->structCount = count; 
     this->structPool = new MyStruct<T>[count]; 
     for(size_t i=0 ; i<count ; i++) { 
      //placement new to call constructor 
      new (&this->structPool[i].object) T(); 
     } 
    } 

    ~MyClass() { 
     for(size_t i=0 ; i<this->structCount ; i++) { 
      //explicit destructor call 
      this->structPool[i].object.~T(); 
     } 
     delete[] this->structPool; 
    } 
} 

我的疑問是,這是一種安全的方式呢?在某些情況下我會犯一些隱藏的錯誤嗎?它是否適用於每種類型的對象(PO​​D和非POD)?

+0

請注意,你的代碼是不例外安全如果其中一個構造函數失敗,會泄漏對象和內存。 – avakar 2010-06-08 06:32:59

回答

7

不,因爲你的構造函數和析構函數都被調用了兩次。因爲你有這樣的:

template <typename T> struct MyStruct { 
    T object; 
} 

當你構建一個MyStruct<T>編譯將構建內T,當你刪除的對象內T會自動調用析構函數。

對於此示例,不需要放置新的或明確的析構函數調用。

如果您分配原始內存,則新放置將很有用。例如,如果您將新的更改爲:

this->structPool = new char[sizeof(T) * count]; 

然後您想要放置新的和explict析構函數調用。

+0

如果'this-> structPool = new MyStruct [count];'被'this-> structPool =(MyStruct *)malloc(sizeof(MyStruct )* count);'替換。在這種情況下,我們需要放置新的和顯式的析構函數? – uray 2010-06-07 23:47:22

+0

是的,你這樣做,但你會調用MyStruct 本身的構造函數/析構函數,而不是MyString .object。 – 2010-06-07 23:49:29

+0

@uray - 是的,如果您使用malloc獲取原始內存,則需要放置新的/顯式的析構函數。也就是說,看起來你正在尋找一個使用佈局新/顯式析構函數的理由,而不是真正需要它。 – 2010-06-08 00:07:40

1

不,這絕對不是遠程安全的做法。當您對非POD T執行new MyStruct<T>[count]時,數組中的每個MyStruct<T>對象都已獲取默認構造,這意味着object成員的構造函數會自動調用。然後,您嘗試在其上執行就地構建(通過值初始化)。結果的行爲是未定義的。

刪除時也存在同樣的問題。

你試圖實現什麼?只要做new MyStruct<T>[count]()(注意多餘的空()),它已經對數組中的每個元素執行了值初始化(正是你之後嘗試「手動」做的事情)。爲什麼你覺得你必須通過就地施工來完成?

同樣,當你做

delete[] this->structPool; 

它會自動要求數組中的每個成員MyStruct<T>::object的析構函數。無需手動完成。

+0

如果'T'的構造函數需要構造參數,'T'具有'T()'和'T(arg,..)'構造函數,但是我需要在構造過程中提供參數?我還可以做'this-> structPool = new MyStruct [count];'建築風格? – uray 2010-06-07 23:53:16

+0

是多餘的空(')'是強制性的?如果我不使用它會發生什麼,它會爲每個元素進行值初始化嗎? – uray 2010-06-08 00:15:59

+0

@uray:如果'T'沒有默認的構造函數,'MyStruct'的定義將不會被編譯。如果'T'有默認的構造函數,但是你想使用其他的構造函數,那麼你用雙重構造(和雙重破壞)來解決上述問題。 – AnT 2010-06-08 00:19:00

0
  1. 記住new總是會調用構造函數,不管它是否放置或不放置。

- 所以你的代碼使用兩次新。這將會調用構造函數兩次。如果你想避免這種情況,可以:

更改您的第一個新進入的malloc(或任何類型的頁頭的)

刪除你的第二個位置的新

  • 刪除數組中的對象,最好的方法是:調用每個對象的析構函數;釋放內存。
  • - 所以你可以做兩種:

    刪除與刪除[]如果您正在使用新的[]

    調用析構函數的每個對象,做一個C風格的自由,如果你正在使用malloc和安置新

    0
    template <typename T> class MyClass { 
        void* structPool; 
        size_t structCount; 
    
        MyClass(size_t count) 
         : structPool(new char[sizeof(T)*count], structCount(count) 
        { 
         //placement new to call constructor 
         for(size_t i=0 ; i<count ; i++) 
          new (structPool+i*sizeof(T)) T(); 
        } 
    
        ~MyClass() { 
         //explicit destructor call 
         for(size_t i=0 ; i<structCount ; i++) 
          reinterpret_cast<T*>(structPool+i*sizeof(T))->~T(); 
         delete[] structPool; 
        } 
    } 
    

    請注意,這不是異常安全:如果一個構造函數導致異常,它不叫上已經構造的對象的析構函數和它泄漏內存。當其中一個析構函數拋出時,也會失敗。

    看看你最喜歡的std lib實現中的std::vector,看看如何做到這一點。然而,這導致了一個問題:爲什麼你想這樣做呢?
    一個std::vector已經做了這一切,確實是正確的,你可以用它開箱即用,每個人都在看你的代碼會立刻理解它:

    template <typename T> class MyClass { 
        std::vector<T> data_; 
        MyClass(size_t count) : data() {data.resize(count);} 
        //~MyClass() not needed 
    } 
    
    相關問題