簡體   English   中英

如何避免std :: vector <>初始化其所有元素?

[英]How can I avoid std::vector<> to initialize all its elements?

編輯:我編輯了問題及其標題更精確。

考慮以下源代碼:

#include <vector>
struct xyz {
    xyz() { } // empty constructor, but the compiler doesn't care
    xyz(const xyz& o): v(o.v) { } 
    xyz& operator=(const xyz& o) { v=o.v; return *this; }
    int v; // <will be initialized to int(), which means 0
};

std::vector<xyz> test() {
    return std::vector<xyz>(1024); // will do a memset() :-(
}

...我怎么能避免vector <>分配的內存用它的第一個元素的副本進行初始化,這是一個O(n)操作我寧願為了速度而跳過,因為我的默認構造函數什么也沒做?

如果不存在通用的解決方案,那么g ++特定的解決方案就可以做到(但我找不到任何屬性來執行此操作)。

編輯 :生成的代碼如下(命令行:arm-elf-g ++ - 4.5 -O3 -S -fno-verbose-asm -o-test.cpp | arm-elf-c ++ filt | grep -vE'^ [[: space:]] + [。@]。* $')

test():
    mov r3, #0
    stmfd   sp!, {r4, lr}
    mov r4, r0
    str r3, [r0, #0]
    str r3, [r0, #4]
    str r3, [r0, #8]
    mov r0, #4096
    bl  operator new(unsigned long)
    add r1, r0, #4096
    add r2, r0, #4080
    str r0, [r4, #0]
    stmib   r4, {r0, r1}
    add r2, r2, #12
    b       .L4          @
.L8:                     @
    add     r0, r0, #4   @
.L4:                     @
    cmp     r0, #0       @  fill the memory
    movne   r3, #0       @
    strne   r3, [r0, #0] @
    cmp     r0, r2       @
    bne     .L8          @
    str r1, [r4, #4]
    mov r0, r4
    ldmfd   sp!, {r4, pc}

編輯:為了完整性,這里是x86_64的程序集:

.globl test()
test():
LFB450:
    pushq   %rbp
LCFI0:
    movq    %rsp, %rbp
LCFI1:
    pushq   %rbx
LCFI2:
    movq    %rdi, %rbx
    subq    $8, %rsp
LCFI3:
    movq    $0, (%rdi)
    movq    $0, 8(%rdi)
    movq    $0, 16(%rdi)
    movl    $4096, %edi
    call    operator new(unsigned long)
    leaq    4096(%rax), %rcx
    movq    %rax, (%rbx)
    movq    %rax, 8(%rbx)
    leaq    4092(%rax), %rdx
    movq    %rcx, 16(%rbx)
    jmp     L4          @
L8:                     @
    addq    $4, %rax    @
L4:                     @
    testq   %rax, %rax  @ memory-filling loop
    je      L2          @
    movl    $0, (%rax)  @
L2:                     @
    cmpq    %rdx, %rax  @
    jne     L8          @
    movq    %rcx, 8(%rbx)
    movq    %rbx, %rax
    addq    $8, %rsp
    popq    %rbx
    leave
LCFI4:
    ret
LFE450:
EH_frame1:
LSCIE1:
LECIE1:
LSFDE1:
LASFDE1:
LEFDE1:

編輯:我認為當你想避免不必要的初始化時,結論是不使用std::vector<> 我最終展開了我自己的模板化容器,它表現得更好(並且具有適用於neon和armv7的專用版本)。

分配的元素的初始化由Allocator模板參數控制,如果您需要自定義,則自定義它。 但請記住,在臟黑客的情況下,這很容易結束,因此請謹慎使用。 例如,這是一個非常臟的解決方案。 它將避免初始化,但它很可能會在性能上更差,但為了演示的緣故(因為人們已經說過這是不可能的!......不可能不是C ++程序員的詞匯!):

template <typename T>
class switch_init_allocator : public std::allocator< T > {
  private:
    bool* should_init;
  public:
    template <typename U>
    struct rebind {
      typedef switch_init_allocator<U> other;
    };

    //provide the required no-throw constructors / destructors:
    switch_init_allocator(bool* aShouldInit = NULL) throw() : std::allocator<T>(), should_init(aShouldInit) { };
    switch_init_allocator(const switch_init_allocator<T>& rhs) throw() : std::allocator<T>(rhs), should_init(rhs.should_init) { };
    template <typename U>
    switch_init_allocator(const switch_init_allocator<U>& rhs, bool* aShouldInit = NULL) throw() : std::allocator<T>(rhs), should_init(aShouldInit) { };
    ~switch_init_allocator() throw() { };

    //import the required typedefs:
    typedef typename std::allocator<T>::value_type value_type;
    typedef typename std::allocator<T>::pointer pointer;
    typedef typename std::allocator<T>::reference reference;
    typedef typename std::allocator<T>::const_pointer const_pointer;
    typedef typename std::allocator<T>::const_reference const_reference;
    typedef typename std::allocator<T>::size_type size_type;
    typedef typename std::allocator<T>::difference_type difference_type;

    //redefine the construct function (hiding the base-class version):
    void construct( pointer p, const_reference cr) {
      if((should_init) && (*should_init))
        new ((void*)p) T ( cr );
      //else, do nothing.
    };
};

template <typename T>
class my_vector : public std::vector<T, switch_init_allocator<T> > {
  public:
    typedef std::vector<T, switch_init_allocator<T> > base_type;
    typedef switch_init_allocator<T> allocator_type;
    typedef std::vector<T, allocator_type > vector_type;
    typedef typename base_type::size_type size_type;
  private:
    bool switch_flag; //the order here is very important!!
    vector_type vec;
  public:  
    my_vector(size_type aCount) : switch_flag(false), vec(aCount, allocator_type(&switch_flag)) { };
    //... and the rest of this wrapper class...
    vector_type& get_vector() { return vec; };
    const vector_type& get_vector() const { return vec; };
    void set_switch(bool value) { switch_flag = value; };
};

class xyz{};

int main(){
  my_vector<xyz> v(1024); //this won't initialize the memory at all.
  v.set_switch(true); //set back to true to turn initialization back on (needed for resizing and such)
}

當然,上面的內容很笨拙而且不推薦,當然也沒有比實際讓內存充滿第一個元素的副本更好(特別是因為使用這個標志檢查會妨礙每個元素構造) 。 但是,當想要優化STL容器中元素的分配和初始化時,這是一個探索的途徑,所以我想展示它。 關鍵是,你可以注入代碼的唯一地方是阻止std :: vector容器調用copy-constructor來初始化你的元素,這是在vector的allocator對象的construct函數中。

此外,您可以取消“切換”並簡單地執行“no-init-allocator”,但是,您還可以關閉在調整大小期間復制數據所需的復制構造(這將使此向量類更多不太有用)。

這是vector一個奇怪的角落。 問題在於你的元素是值初始化的......而是第一個原型元素中的隨機內容被復制到向量中的所有其他元素。 (此行為隨C ++ 11而改變,該值初始化每個元素)。

這是(/ was)完成的原因:考慮一些引用計數對象...如果構造一個vector要求初始化為這樣一個對象的1000個元素,你顯然想要一個引用計數為1000的對象,而不是1000個獨立的“克隆”。 我說“顯然”是因為首先計算了對象引用意味着非常需要。

無論如何,你幾乎沒有運氣。 實際上, vector確保所有元素都是相同的,即使它同步的內容恰好是未初始化的垃圾。


在非標准g ++特定的快樂黑客的領域,我們可以利用vector接口中的任何公共模板化成員函數作為后門來簡單地通過專門化某些新類型的模板來更改私有成員數據。

警告 :不僅僅是為了這個“解決方案”,而是為了避免默認構造的整個工作... 不要對具有重要不變量的類型執行此操作 - 您打破封裝並且可以輕松地使用vector本身或您嘗試調用operator=()某些操作operator=() ,復制構造函數和/或析構函數,其中*this / left-和/或右側參數不遵守這些不變量。 例如,避免使用您希望為NULL的指針的值類型或有效對象,引用計數器,資源句柄等。

#include <iostream>
#include <vector>

struct Uninitialised_Resize
{
    explicit Uninitialised_Resize(int n) : n_(n) { }
    explicit Uninitialised_Resize() { }
    int n_;
};

namespace std
{
    template <>
    template <>
    void vector<int>::assign(Uninitialised_Resize ur, Uninitialised_Resize)
    {
        this->_M_impl._M_finish = this->_M_impl._M_start + ur.n_;

        // note: a simpler alternative (doesn't need "n_") is to set...
        //   this->_M_impl._M_finish = this->_M_impl._M_end_of_storage;
        // ...which means size() will become capacity(), which may be more
        // you reserved() (due to rounding; good) or have data for
        // (bad if you have to track in-use elements elsewhere,
        //  which makes the situation equivalent to just reserve()),
        // but if you can somehow use the extra elements then all's good.
    }
}

int main()
{
    {
        // try to get some non-0 values on heap ready for recycling...
        std::vector<int> x(10000);
        for (int i = 0; i < x.size(); ++i)
            x[i] = i;
    }

    std::vector<int> x;
    x.reserve(10000);
    for (int i = 1; i < x.capacity(); ++i)
        if (x[0] != x[i])
        {
            std::cout << "lucky\n";
            break;
        }
    x.assign(Uninitialised_Resize(1000), Uninitialised_Resize());

    for (int i = 1; i < x.size(); ++i)
        if (x[0] != x[i])
        {
            std::cout << "success [0] " << x[0] << " != [" << i << "] "
                << x[i] << '\n';
            break;
        }
}

我的輸出:

lucky
success [0] 0 != [1] 1

這表明新的vector被重新分配到第一個向量在超出范圍時釋放的堆,並顯示值不會被賦值所破壞。 當然,如果不仔細檢查vector源,就無法知道其他一些重要的類不變量是否已經失效,私人成員的確切名稱/導入可能隨時變化....

您將所有基元包裝在結構中:

struct IntStruct
{
    IntStruct();

    int myInt;
}

將IntStruct()定義為空構造函數。 因此,您將v聲明為IntStruct v; 因此,當xyzsvector全部為值初始化時,它們所做的只是值初始化v,這是一個無操作。

編輯:我誤解了這個問題。 如果你有一個原始類型的vector ,你應該這樣做,因為vector被定義為在通過resize()方法創建元素時進行值初始化。 在構造時,結構不需要對其成員進行值初始化,盡管這些“未初始化”的值仍然可以通過其他東西設置為0 - 嘿,它們可以是任何東西。

你無法避免std :: vector的元素初始化。

因此,我使用std :: vector派生類。 resize()在此示例中實現。 您也必須實現構造函數。

雖然這不是標准的C ++而是編譯器實現:-(

#include <vector>

template<typename _Tp, typename _Alloc = std::allocator<_Tp>>
class uvector : public std::vector<_Tp, _Alloc>
{
    typedef std::vector<_Tp, _Alloc> parent;
    using parent::_M_impl;

public:
    using parent::capacity;
    using parent::reserve;
    using parent::size;
    using typename parent::size_type;

    void resize(size_type sz)
    {
        if (sz <= size())
            parent::resize(sz);
        else
        {
            if (sz > capacity()) reserve(sz);
            _M_impl._M_finish = _M_impl._M_start + sz;
        }
    }
};

我沒有看到內存初始化。 默認的int()構造函數什么都不做,就像在C中一樣。

程序:

#include <iostream>
#include <vector>

struct xyz {
    xyz() {}
    xyz(const xyz& o): v(o.v) {} 
    xyz& operator=(const xyz& o) { v=o.v; return *this; }
    int v;
};

std::vector<xyz> test() {
    return std::vector<xyz>(1024);
}

int main()
{
    std::vector<xyz> foo = test();
    for(int i = 0; i < 10; ++i)
    {
        std::cout << i << ": " << foo[i].v << std::endl;
    }
    return 0;
}

輸出:

$ g++ -o foo foo.cc
$ ./foo 
0: 1606418432
1: 1606418432
2: 1606418432
3: 1606418432
4: 1606418432
5: 1606418432
6: 1606418432
7: 1606418432
8: 1606418432
9: 1606418432

編輯:

如果您只是嘗試將向量初始化為一些重要的東西,並且不想浪費時間默認構造其內容,您可能想嘗試創建自定義迭代器並將其傳遞給向量的構造函數。

修改示例:

#include <iostream>
#include <vector>
#include <iterator>

struct xyz {
    xyz() {}
    xyz(int init): v(init) {}
    xyz(const xyz& o): v(o.v) {} 
    xyz& operator=(const xyz& o) { v=o.v; return *this; }
    int v;
};

class XYZInitIterator: public std::iterator<std::input_iterator_tag, xyz>
{
public:
                        XYZInitIterator(int init): count(init) {}
                        XYZInitIterator(const XYZInitIterator& iter)
                        : count(iter.count) {}
    XYZInitIterator&    operator=(const XYZInitIterator& iter)
                        { count = iter.count; return *this; }
    value_type          operator*() const { return xyz(count); }
    bool                operator==(const XYZInitIterator& other) const 
                        { return count == other.count; }
    bool                operator!=(const XYZInitIterator& other) const 
                        { return count != other.count; }
    value_type          operator++() { return xyz(++count); }
    value_type          operator++(int) { return xyz(count++); }
private:
    int count;
};

std::vector<xyz> test() {
    XYZInitIterator start(0), end(1024);
    return std::vector<xyz>(start, end);
}

int main()
{
    std::vector<xyz> foo = test();
    for(int i = 0; i < 10; ++i)
    {
        std::cout << std::dec << i << ": " << std::hex << foo[i].v << std::endl;
    }
    return 0;
}

輸出:

$ g++ -o foo foo.cc
$ ./foo 
0: 0
1: 1
2: 2
3: 3
4: 4
5: 5
6: 6
7: 7
8: 8
9: 9

作為參考,以下代碼導致g ++中的最佳組裝: 我不是說我會使用它而我不鼓勵你。 它不適合C ++! 這是一個非常非常骯臟的黑客! 我想它甚至可能依賴於g ++版本,所以,真的,不要使用它。 如果我看到它用在某處,我會嘔吐。

#include <vector>

template<typename T>
static T create_uninitialized(size_t size, size_t capacity) {
    T v;
#if defined(__GNUC__)
    // Don't say it. I know -_-;
    // Oddly, _M_impl is public in _Vector_base !?
    typedef typename T::value_type     value_type;
    typedef typename T::allocator_type allocator_type;
    typedef std::_Vector_base<value_type, allocator_type> base_type;
    base_type& xb(reinterpret_cast<base_type&>(v));
    value_type* p(new value_type[capacity]);
#if !defined(__EXCEPTIONS)
    size=p?size:0;         // size=0 if p is null
    capacity=p?capacity:0; // capacity=0 if p is null
#endif
    capacity=std::max(size, capacity); // ensure size<=capacity
    xb._M_impl._M_start = p;
    xb._M_impl._M_finish = p+size;
    xb._M_impl._M_end_of_storage = p+capacity;
#else
    // Fallback, for the other compilers
    capacity=std::max(size, capacity);
    v.reserve(capacity);
    v.resize(size);
#endif
    return v;
}

struct xyz {
    // empty default constructor
    xyz() { }
    xyz(const xyz& o): v(o.v) { }
    xyz& operator=(const xyz& o) { v=o.v; return *this; }
    int v;
    typedef std::vector<xyz> vector;
};

// test functions for assembly dump
extern xyz::vector xyz_create() {
    // Create an uninitialized vector of 12 elements, with
    // a capacity to hold 256 elements.
    return create_uninitialized<xyz::vector>(12,256);
}

extern void xyz_fill(xyz::vector& x) {
    // Assign some values for testing
    for (int i(0); i<x.size(); ++i) x[i].v = i;
}

// test
#include <iostream>
int main() {
    xyz::vector x(xyz_create());
    xyz_fill(x);
    // Dump the vector
    for (int i(0); i<x.size(); ++i) std::cerr << x[i].v << "\n";
    return 0;
}

編輯:實現_Vector_impl是公開的,這簡化了事情。

編輯:這是為xyz_create()生成的ARM程序集,使用-fno-exceptions(使用c ++ filt進行解碼)編譯並且沒有任何內存初始化循環:

xyz_create():
    mov r3, #0
    stmfd   sp!, {r4, lr}
    mov r4, r0
    str r3, [r0, #0]
    str r3, [r0, #4]
    str r3, [r0, #8]
    mov r0, #1024
    bl  operator new[](unsigned long)(PLT)
    cmp r0, #0
    moveq   r3, r0
    movne   r3, #1024
    moveq   r2, r0
    movne   r2, #48
    add r2, r0, r2
    add r3, r0, r3
    stmia   r4, {r0, r2, r3}    @ phole stm
    mov r0, r4
    ldmfd   sp!, {r4, pc}

..和x86_64這里:

xyz_create():
    pushq   %rbp
    movq    %rsp, %rbp
    pushq   %rbx
    movq    %rdi, %rbx
    subq    $8, %rsp
    movq    $0, (%rdi)
    movq    $0, 8(%rdi)
    movq    $0, 16(%rdi)
    movl    $1024, %edi
    call    operator new[](unsigned long)
    cmpq    $1, %rax
    movq    %rax, (%rbx)
    sbbq    %rdx, %rdx
    notq    %rdx
    andl    $1024, %edx
    cmpq    $1, %rax
    sbbq    %rcx, %rcx
    leaq    (%rax,%rdx), %rdx
    notq    %rcx
    andl    $48, %ecx
    movq    %rdx, 16(%rbx)
    leaq    (%rax,%rcx), %rcx
    movq    %rbx, %rax
    movq    %rcx, 8(%rbx)
    addq    $8, %rsp
    popq    %rbx
    leave
    ret

我也很好奇。 你是否只想將內存隨機初始化?

向量元素存儲在連續的存儲器位置中,因此隨機初始化是可能的。

如果您想要一個僅保留內存但沒有初始化元素的向量,請使用reserve而不是構造函數:

std::vector<xyz> v;
v.reserve(1024);
assert(v.capacity() >= 1024);
assert(v.size() == 0);

使用此方法聲明struct的方式,沒有機制來默認初始化結構的int成員,因此您獲得默認的C行為,這是一個不確定的初始化。 為了使用默認初始化值初始化int成員變量,您必須將其添加到結構構造函數的初始化列表中。 例如,

struct xyz {
    xyz(): v() { } //initialization list sets the value of int v to 0
    int v;
};

其中,作為

struct xyz {
    xyz(): { } //no initialization list, therefore 'v' remains uninitialized
    int v;
};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM