![](/img/trans.png)
[英]Can I list-initialize std::vector with perfect forwarding of the elements?
[英]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;
因此,當xyzs
的vector
全部為值初始化時,它們所做的只是值初始化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.