2008-09-18 32 views
34

我正在寫一個內部循環,需要將struct放在連續存儲器中。我不知道有多少這些struct會提前。我的問題是STL的vector將它的值初始化爲0,所以無論我做什麼,我都會產生初始化的成本以及將struct的成員設置爲其值的成本。帶有未初始化存儲的STL向量?

有沒有什麼辦法來防止初始化,或者是否有一個類似STL的容器與可調整大小的連續存儲和未初始化元素?

(我敢肯定的是,這部分代碼需要優化,我敢肯定,初始化是顯著的成本。)

另外,請參閱下面我的意見了澄清,當初始化發生。

一些代碼:

void GetsCalledALot(int* data1, int* data2, int count) { 
    int mvSize = memberVector.size() 
    memberVector.resize(mvSize + count); // causes 0-initialization 

    for (int i = 0; i < count; ++i) { 
     memberVector[mvSize + i].d1 = data1[i]; 
     memberVector[mvSize + i].d2 = data2[i]; 
    } 
} 
+1

注 - 使用儲備()不是一個解決方案,因爲你不能合法訪問是在位置結束()和上面的數據。 – 2008-09-18 20:36:23

+1

另一個解釋:並不是構造函數將值初始化爲0.這就是調整大小調用插入,這是。 – 2008-09-18 20:42:06

+0

你能否給我們結構聲明?謝謝... :-) – paercebal 2008-09-18 21:00:26

回答

23

std::vector必須以某種方式初始化數組中的值,這意味着必須調用某個構造函數(或複製構造函數)。 vector(或任何容器類)的行爲是未定義的,如果您要像初始化那樣訪問數組的未初始化部分。

最好的方法是使用reserve()push_back(),以便使用複製構造函數,避免默認構造。

使用您的示例代碼:

struct YourData { 
    int d1; 
    int d2; 
    YourData(int v1, int v2) : d1(v1), d2(v2) {} 
}; 

std::vector<YourData> memberVector; 

void GetsCalledALot(int* data1, int* data2, int count) { 
    int mvSize = memberVector.size(); 

    // Does not initialize the extra elements 
    memberVector.reserve(mvSize + count); 

    // Note: consider using std::generate_n or std::copy instead of this loop. 
    for (int i = 0; i < count; ++i) { 
     // Copy construct using a temporary. 
     memberVector.push_back(YourData(data1[i], data2[i])); 
    } 
} 

與調用reserve()(或resize())唯一的問題是這樣的,你可能會更經常比你需要最終調用拷貝構造函數。如果您可以對陣列的最終尺寸做出很好的預測,那麼最好在開始時對空間進行一次處理。如果你不知道最終尺寸,至少平均拷貝數是最小的。

在當前版本的C++中,內部循環的效率有點低,因爲臨時值在堆棧上構建,複製構建到向量內存,最後臨時被銷燬。然而,下一版本的C++有一個稱爲R-Value引用(T&&)的功能,這將有所幫助。

std::vector提供的接口不允許使用其他選項,即使用某些類似工廠的類來構造非默認值。下面是這個模式看起來像在C++中實現的一個粗略示例:

template <typename T> 
class my_vector_replacement { 

    // ... 

    template <typename F> 
    my_vector::push_back_using_factory(F factory) { 
     // ... check size of array, and resize if needed. 

     // Copy construct using placement new, 
     new(arrayData+end) T(factory()) 
     end += sizeof(T); 
    } 

    char* arrayData; 
    size_t end; // Of initialized data in arrayData 
}; 

// One of many possible implementations 
struct MyFactory { 
    MyFactory(int* p1, int* p2) : d1(p1), d2(p2) {} 
    YourData operator()() const { 
     return YourData(*d1,*d2); 
    } 
    int* d1; 
    int* d2; 
}; 

void GetsCalledALot(int* data1, int* data2, int count) { 
    // ... Still will need the same call to a reserve() type function. 

    // Note: consider using std::generate_n or std::copy instead of this loop. 
    for (int i = 0; i < count; ++i) { 
     // Copy construct using a factory 
     memberVector.push_back_using_factory(MyFactory(data1+i, data2+i)); 
    } 
} 

這樣做意味着您必須創建自己的向量類。在這種情況下,它也使本應該是一個簡單的例子變得複雜。但是有時候使用像這樣的工廠函數會更好,例如,如果插入是以其他值爲條件的,那麼即使實際上不需要,也必須無條件地構造一些昂貴的臨時對象。

1

使用std ::矢量::儲備()方法。它不會調整矢量大小,但會分配空間。

3

嗯...

想盡了辦法:

std::vector<T>::reserve(x) 

它將使你保留足夠的內存對於x項目沒有任何初始化(你的載體仍然是空的)。因此,直到x纔會重新分配。

第二點是矢量不會將值初始化爲零。你在調試中測試你的代碼嗎?

驗證的G ++,下面的代碼後:

#include <iostream> 
#include <vector> 

struct MyStruct 
{ 
    int m_iValue00 ; 
    int m_iValue01 ; 
} ; 

int main() 
{ 
    MyStruct aaa, bbb, ccc ; 

    std::vector<MyStruct> aMyStruct ; 

    aMyStruct.push_back(aaa) ; 
    aMyStruct.push_back(bbb) ; 
    aMyStruct.push_back(ccc) ; 

    aMyStruct.resize(6) ; // [EDIT] double the size 

    for(std::vector<MyStruct>::size_type i = 0, iMax = aMyStruct.size(); i < iMax; ++i) 
    { 
     std::cout << "[" << i << "] : " << aMyStruct[i].m_iValue00 << ", " << aMyStruct[0].m_iValue01 << "\n" ; 
    } 

    return 0 ; 
} 

得出以下結果:

[0] : 134515780, -16121856 
[1] : 134554052, -16121856 
[2] : 134544501, -16121856 
[3] : 0, -16121856 
[4] : 0, -16121856 
[5] : 0, -16121856 

你所看見的初始化可能是一個假象。

[編輯]在對調整大小的評論後,我修改了代碼以添加調整大小的行。調整大小有效地調用了矢量內的對象的默認構造函數,但如果默認的構造函數什麼也不做,那麼什麼都不會初始化......我仍然相信它是一個神器(我第一次管理整個矢量與下面的代碼:

aMyStruct.push_back(MyStruct()) ; 
aMyStruct.push_back(MyStruct()) ; 
aMyStruct.push_back(MyStruct()) ; 

所以... : - 。/

[編輯2]已經長Arkadiy提供的一樣,解決方法是使用內聯構造函數取所需的參數喜歡的東西

struct MyStruct 
{ 
    MyStruct(int p_d1, int p_d2) : d1(p_d1), d2(p_d2) {} 
    int d1, d2 ; 
} ; 

這可能會在你的代碼中被內聯。

但是,您應該無論如何都應該使用探查器研究您的代碼,以確保這段代碼是您的應用程序的瓶頸。

+0

我上面寫了一個註釋。它不是vector的構造函數,它初始化爲0.它是resize()。 – 2008-09-18 20:46:52

0

結構本身是否需要在連續的內存中,或者你可以逃脫擁有一個struct *向量?

向量創建任何添加到它們的副本,因此使用指針向量而不是對象是改進性能的一種方法。

0

我不認爲STL是你的答案。你將需要使用realloc()來推出你自己的解決方案。你必須存儲一個指針和大小或元素數量,並使用它來查找在realloc()之後從哪裏開始添加元素。

int *memberArray; 
int arrayCount; 
void GetsCalledALot(int* data1, int* data2, int count) { 
    memberArray = realloc(memberArray, sizeof(int) * (arrayCount + count); 
    for (int i = 0; i < count; ++i) { 
     memberArray[arrayCount + i].d1 = data1[i]; 
     memberArray[arrayCount + i].d2 = data2[i]; 
    } 
    arrayCount += count; 
} 
4

所以,這裏的問題,調整大小調用插入,這是從每個新添加的元素的默認構造元素進行復制構造。爲了得到0成本,你需要編寫你自己的默認構造函數和你自己的拷貝構造函數作爲空函數。這樣做到你的拷貝構造函數是非常糟糕的想法,因爲它會打破std :: vector的內部重新分配算法。

摘要:你不能用std :: vector做到這一點。

8

爲了說明reserve()響應:您需要將reserve()與push_back()一起使用。這樣,每個元素都不會調用默認構造函數,而是調用複製構造函數。你仍然會受到在堆棧上設置你的結構體的懲罰,然後將它複製到向量中。另一方面,如果使用

vect.push_back(MyStruct(fieldValue1, fieldValue2)) 

編譯器將直接在屬於該向量的內存中構建新實例。這取決於優化器的智能程度。您需要檢查生成的代碼以找出答案。

1

從您的意見到其他海報,它看起來像你離開malloc()和朋友。向量不會讓你有未構造的元素。

1

從你的代碼看起來你有一個結構向量,每個結構包含2個整數。你可以使用2個ints向量嗎?然後

copy(data1, data1 + count, back_inserter(v1)); 
copy(data2, data2 + count, back_inserter(v2)); 

現在您不需要爲每次複製結構付費。

0

我會做這樣的事情:

void GetsCalledALot(int* data1, int* data2, int count) 
{ 
    const size_t mvSize = memberVector.size(); 
    memberVector.reserve(mvSize + count); 

    for (int i = 0; i < count; ++i) { 
    memberVector.push_back(MyType(data1[i], data2[i])); 
    } 
} 

您需要定義存儲在memberVector類型一個構造函數,但是這是因爲它會給你兩全其美的小的成本;沒有不必要的初始化,並且在循環過程中不會發生重新分配。

10

的C++ 0x補充說,被完全排除任何的臨時的新成員函數模板emplace_backvector(這依賴於可變參數模板和完善的轉發):

memberVector.emplace_back(data1[i], data2[i]); 
1

如果你真的堅持擁有的元素未初始化並犧牲一些像front(),back(),push_back()這樣的方法,從數字中使用boost向量。它允許你甚至在調用resize()時不保存現有元素...

6

在C++ 11(和boost)中,你可以使用unique_ptr的陣列版本來分配未初始化的數組。這不是一個stl容器,但仍然是內存管理和C++ - ish,這對於許多應用程序來說已經足夠了。

auto my_uninit_array = std::unique_ptr<mystruct[]>(new mystruct[count]); 
1

你可以在你的元素類型周圍使用一個包裝類型,並且默認的構造函數什麼都不做。例如: -

template <typename T> 
struct no_init 
{ 
    T value; 

    no_init() { static_assert(std::is_standard_layout<no_init<T>>::value && sizeof(T) == sizeof(no_init<T>), "T does not have standard layout"); } 

    no_init(T& v) { value = v; } 
    T& operator=(T& v) { value = v; return value; } 

    no_init(no_init<T>& n) { value = n.value; } 
    no_init(no_init<T>&& n) { value = std::move(n.value); } 
    T& operator=(no_init<T>& n) { value = n.value; return this; } 
    T& operator=(no_init<T>&& n) { value = std::move(n.value); return this; } 

    T* operator&() { return &value; } // So you can use &(vec[0]) etc. 
}; 

要使用:

std::vector<no_init<char>> vec; 
vec.resize(2ul * 1024ul * 1024ul * 1024ul);