Как я могу избежать std::vector <> для инициализации всех его элементов?
EDIT: Я отредактировал и вопрос, и его название, чтобы быть более точным.
Учитывая следующий исходный код:
#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() :-(
}
... как я могу избежать выделения памяти, выделенной вектором < > для инициализации копиями своего первого элемента, который является операцией O (n), которую я бы скорее пропустил ради скорости, поскольку мой дефолт конструктор ничего не делает?
Решение, специфичное для g++, будет выполнено, если не существует универсального (но я не смог найти какой-либо атрибут для этого).
EDIT: сгенерированный код следует (командная строка: arm-elf-g++ - 4.5 -O3 -S -fno-verbose-asm -o - test.cpp | arm-elf-С++ filter | 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}
EDIT: Для полноты, вот сборка для 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:
EDIT: Я думаю, что вывод заключается в том, чтобы не использовать std::vector<>
, когда вы хотите избежать ненужной инициализации. Я закончил разворачивать свой собственный шаблонный контейнер, который работает лучше (и имеет специализированные версии для neon и armv7).
Ответы
Ответ 1
Инициализация выделенных элементов контролируется аргументом шаблона Allocator, если вам нужно его настроить, настройте его. Но помните, что это может легко уйти в область грязного взлома, поэтому используйте с осторожностью. Например, вот довольно грязное решение. Это позволит избежать инициализации, но, скорее всего, будет хуже в производительности, но ради демонстрации (поскольку люди сказали, что это невозможно!... невозможно не в словаре программиста на С++!):
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 от вызова конструктора-копии для инициализации ваших элементов, находится в функции построения объекта вектор-распределителя.
Кроме того, вы можете покончить с "коммутатором" и просто сделать "no-init-allocator", но затем вы также отключите построение копии, которое необходимо для копирования данных во время изменения размера (что сделало бы это векторный класс гораздо менее полезен).
Ответ 2
Это странный угол vector
. Проблема заключается не в том, что ваш элемент инициализируется значением... это то, что случайный контент в первом прототипном элементе копируется ко всем остальным элементам вектора. (Это поведение изменилось с С++ 11, значение которого инициализирует каждый элемент).
Это (/было) сделано по уважительной причине: рассмотрим некоторый ссылочный подсчитанный объект... если вы построите vector
запрос на 1000 элементов, инициализированных таким объектом, вы, очевидно, хотите, чтобы один объект со ссылочным числом 1000, вместо того, чтобы иметь 1000 независимых "клонов". Я говорю "очевидно", потому что, сделав ссылку на объект, подсчитанную в первую очередь, подразумевает, что это очень желательно.
В любом случае, вам почти не повезло. Фактически, vector
обеспечивает, чтобы все элементы были одинаковыми, даже если контент, который он синхронизирует, является неинициализированным мусором.
В стране с нестандартным g++-специфическим счастливым взломом мы можем использовать любую публичную шаблонную функцию-член в интерфейсе vector
в качестве бэкдора для изменения данных частного члена, просто специализируясь на шаблоне для какого-либо нового типа.
ПРЕДУПРЕЖДЕНИЕ: не только для этого "решения", но и для всего этого, чтобы избежать построения по умолчанию... не делайте этого для типов с важными инвариантами - вы разрушаете инкапсуляцию и можете легко иметь vector
или какую-либо операцию, которую вы пытаетесь вызвать operator=()
, copy-constructors и/или деструкторы, где аргументы *this
/left и/или right-side не соблюдают эти инварианты. Например, избегайте типов значений с указателями, которые вы ожидаете быть 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 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
, а точные имена/импорт частных членов могут меняться в любое время....
Ответ 3
Вы переносите все свои примитивы в структуру:
struct IntStruct
{
IntStruct();
int myInt;
}
с IntStruct(), определяемым как пустой конструктор. Таким образом, вы объявляете v
как IntStruct v;
, поэтому, когда a vector
of xyzs
все значения инициализируются, все, что они делают, это инициализация значения v, которая является не-op.
EDIT: Я неправильно понял вопрос. Это то, что вы должны делать, если у вас есть vector
примитивных типов, потому что vector
определяется значением-initialize при создании элементов с помощью метода resize()
. Структуры не требуются для инициализации своих членов при построении, хотя эти "неинициализированные" значения по-прежнему могут быть установлены на 0 чем-то другим - эй, они могут быть любыми.
Ответ 4
Я не вижу инициализацию памяти. Конструктор 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
EDIT:
Если вы просто пытаетесь инициализировать вектор некоторой нетривиальной вещью и не хотите тратить время на создание по умолчанию своего содержимого, вы можете попробовать создать пользовательский итератор и передать его векторному конструктору.
Модифицированный пример:
#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
Ответ 5
Для справки следующий код приводит к оптимальной сборке в g++:
Я не говорю, что когда-либо буду использовать его, и я не призываю вас. Это не правильно С++! Это очень и очень грязный хак! Я думаю, что это может даже зависеть от версии 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;
}
EDIT: реализованный _Vector_impl
был общедоступным, что упростило ситуацию.
EDIT:. Это созданная сборка ARM для xyz_create(), скомпилированная с -fno-исключениями (demangled using С++ 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
Ответ 6
Вы не можете избежать инициализации элементов std::vector.
По этой причине я использую производный класс std::vector.
resize()
реализован в этом примере. Вы должны также реализовать конструкторы.
Хотя это не стандартная С++, а реализация компилятора: - (
#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;
}
}
};
Ответ 7
Мне также любопытно. Вы просто хотите, чтобы память была инициализирована случайным образом?
Векторные элементы хранятся в последовательных ячейках памяти, поэтому возможна случайная инициализация.
Ответ 8
Если вам нужен вектор с зарезервированной памятью, но без инициализированных элементов, используйте reserve
вместо конструктора:
std::vector<xyz> v;
v.reserve(1024);
assert(v.capacity() >= 1024);
assert(v.size() == 0);
Ответ 9
Когда ваш 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;
};