如何避免 std::vector<>初始化它的所有元素?
编辑:我编辑了问题及其标题以使其更加准确。
考虑以下源代码:
#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++ 特定的解决方案。
编辑:生成的代码如下(命令行: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 的专用版本)。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
分配的元素的初始化由 Allocator 模板参数控制,如果您需要自定义,请自定义它。但请记住,这很容易陷入肮脏的黑客攻击领域,因此请谨慎使用。例如,这是一个非常肮脏的解决方案。它将避免初始化,但它的性能很可能会更差,但为了演示(正如人们所说这是不可能的!...不可能不在 C++ 程序员的词汇中!):
当然,上面的情况很尴尬并且不推荐,并且肯定不会比实际让内存充满第一个元素的副本更好(特别是因为使用此标志检查将阻碍每个元素的构造)。但在寻求优化 STL 容器中元素的分配和初始化时,这是一个值得探索的途径,所以我想展示它。要点是,唯一可以注入阻止 std::vector 容器调用复制构造函数来初始化元素的代码的地方是向量分配器对象的构造函数。
另外,您可以取消“开关”并简单地执行“no-init-allocator”,但是随后,您还可以关闭在调整大小期间复制数据所需的复制构造(这将使该向量类变得更加重要)不太有用)。
The initialization of the elements allocated is controlled by the Allocator template argument, if you need it customized, customize it. But remember that this can get easily wind-up in the realm of dirty hacking, so use with caution. For instance, here is a pretty dirty solution. It will avoid the initialization, but it most probably will be worse in performance, but for demonstration's sake (as people have said this is impossible!... impossible is not in a C++ programmer's vocabulary!):
Of course, the above is awkward and not recommended, and certainly won't be any better than actually letting the memory get filled with copies of the first element (especially since the use of this flag-checking will impede on each element-construction). But it is an avenue to explore when looking to optimize the allocation and initialization of elements in an STL container, so I wanted to show it. The point is that the only place you can inject code that will stop the std::vector container from calling the copy-constructor to initialize your elements is in the construct function of the vector's allocator object.
Also, you could do away with the "switch" and simply do a "no-init-allocator", but then, you also turn off copy-construction which is needed to copy the data during resizing (which would make this vector class much less useful).
这是向量的一个奇怪的角落。问题是不是您的元素正在被值初始化...而是第一个原型元素中的随机内容被复制到向量中的所有其他元素。 (这种行为在 C++11 中发生了变化,该值初始化每个元素)。
这样做有一个很好的理由:考虑一些引用计数的对象...如果您构造一个向量,要求将 1000 个元素初始化为这样一个对象,那么您显然需要一个带有引用的对象计数为 1000,而不是拥有 1000 个独立的“克隆”。我说“显然”是因为首先对对象引用进行计数意味着这是非常可取的。
无论如何,你几乎不走运了。实际上,
vector
确保所有元素都相同,即使它同步的内容恰好是未初始化的垃圾。在非标准 g++ 特定的快乐黑客领域,我们可以利用
vector
接口中的任何公共模板化成员函数作为后门,只需将模板专门化为某种新类型即可更改私有成员数据。警告:不仅仅是为了这个“解决方案”,而是为了避免默认构造的整个努力......不要对具有重要不变量的类型这样做 - 你会破坏封装并且可以轻松地让
vector
本身或您尝试调用operator=()
的某些操作、复制构造函数和/或析构函数(其中*this
/left-)和/或右侧参数不遵守这些不变量。例如,避免使用您期望为 NULL 的指针或指向有效对象、引用计数器、资源句柄等的值类型。我的输出:
这表明新的向量被重新分配到第一个向量的堆上。当向量超出范围时被释放,并显示值没有被分配破坏。当然,如果不仔细检查向量源,就无法知道其他一些重要的类不变量是否已失效,并且私有成员的确切名称/导入可能随时发生变化......
This is a strange corner of the
vector
. The problem is not that your element is being value initialised... it's that the random content in the first prototypal element is copied to all the other elements in the vector. (This behaviour changed with C++11, which value initialises each element).This is(/was) done for a good reason: consider some reference counted object... if you construct a
vector
asking for 1000 elements initialised to such an object, you obviously want one object with a reference count of 1000, rather than having 1000 independent "clones". I say "obviously" because having made the object reference counted in the first place implies that's highly desirable.Anyway, you're almost out of luck. Effectively, the
vector
is ensuring that all the elements are the same, even if the content it's syncing to happens to be uninitialised garbage.In the land of non-Standard g++-specific happy-hacking, we can exploit any public templated member function in the
vector
interface as a backdoor to change private member data simply by specialising the template for some new type.WARNING: not just for this "solution" but for this whole effort to avoid default construction... don't do this for types with important invariants - you break encapsulation and can easily have
vector
itself or some operation you attempt invokeoperator=()
, copy-constructors and/or destructors where*this
/left- and/or right-hand-side arguments don't honour those invariants. For example, avoid value-types with pointers that you expect to be NULL or to valid objects, reference counters, resource handles etc..My output:
This suggests the new
vector
was reallocated the heap that the first vector released when it went out of scope, and shows the values aren't clobbered by the assign. Of course, there's no way to know if some other important class invariants have been invalidated without inspecting thevector
sources very carefully, and the exact names/import of private members can vary at any time....您将所有基元包装在一个结构中:
将 IntStruct() 定义为空构造函数。因此,您将
v
声明为IntStruct v;
,因此当xyzs
的向量
全部值初始化时,它们所做的一切是值初始化 v,它是一个无操作。编辑:我误读了这个问题。如果您有基本类型的
vector
,则应该执行此操作,因为vector
被定义为在通过resize()
创建元素时进行值初始化方法。结构体不需要在构造时对其成员进行值初始化,尽管这些“未初始化”的值仍然可以通过其他方式设置为 0 —— 嘿,它们可以是任何东西。You wrap all your primitives in a struct:
with IntStruct() defined as an empty constructor. Thus you declare
v
asIntStruct v;
so when avector
ofxyzs
all value-initialize, all they do is value-initialize v which is a no-op.EDIT: I misread the question. This is what you should do if you have a
vector
of primitive types, becausevector
is defined to value-initialize upon creating elements through theresize()
method. Structs are not required to value-initialize their members upon construction, though these "uninitialized" values can still be set to 0 by something else -- hey, they could be anything.作为参考,以下代码可在 g++ 中实现最佳汇编:
我并不是说我会使用它,也不鼓励您这样做。这不是正确的C++!这是一个非常非常肮脏的黑客!我想它甚至可能取决于 g++ 版本,所以,真的,不要使用它。如果我看到它在某个地方被使用,我会呕吐。
编辑:意识到
_Vector_impl
是公开的,这简化了事情。编辑:这是为 xyz_create() 生成的 ARM 程序集,使用 -fno-exceptions 进行编译(使用 c++filt 进行分解),并且没有任何内存初始化循环:
..这里是 x86_64 的:
For reference, the following code leads to optimal assembly in g++:
I am not saying I will ever use it and I don't encourage you to. It is not proper C++! It's a very, very dirty hack! I guess it might even depend on g++ version, so, really, do not use it. I would puke if I saw it used somewhere.
EDIT: realized
_Vector_impl
was public, which simplify things.EDIT: here is the generated ARM assembly for xyz_create(), compiled with -fno-exceptions (demangled using c++filt) and without any memory-initializing loop:
..and here for x86_64:
您无法避免 std::vector 的元素初始化。
由于这个原因,我使用 std::vector 派生类。
resize()
在此示例中实现。您还必须实现构造函数。虽然这不是标准 C++,而是编译器实现:-(
You cannot avoid std::vector's elements initialization.
I use a std::vector derived class for that reason.
resize()
is implemented in this example. You must implement constructors too.Although this is not standard C++ but compiler implementation :-(
我也很好奇。您只想随机初始化内存吗?
向量元素存储在连续的内存位置中,因此可以进行随机初始化。
I am also curious. Do you just want the memory random initialized?
Vector elements are stored in consecutive memory locations so random initialization is a possibility.
我没有看到内存初始化。默认的
int()
构造函数不执行任何操作,就像在 C 中一样。程序:
输出:
编辑:
如果您只是尝试将向量初始化为一些不平凡的东西,并且不想浪费时间默认构造其内容,您可能想尝试创建一个自定义迭代器并将其传递给向量的构造函数。
修改后的示例:
输出:
I'm not seeing the memory initialized. The default
int()
constructor does nothing, just like in C.Program:
Output:
EDIT:
If you're just trying to initialize the vector to some nontrivial thing, and don't want to waste time default-constructing its contents, you might want to try creating a custom iterator and passing it to the vector's constructor.
Modified example:
Output:
如果您想要一个仅保留内存但没有初始化元素的向量,请使用
reserve
而不是构造函数:If you want a vector with only memory reserved, but no initialized elements, use
reserve
instead of the constructor:按照目前声明
struct
的方式,没有机制可以默认初始化结构的int
成员,因此您会得到默认的 C 行为,这是不确定的初始化。为了使用默认初始化值初始化int
成员变量,您必须将其添加到结构构造函数的初始化列表中。例如,当
With the way your
struct
is declared at the moment, there is no mechanism to default initialize theint
member of your struct, therefore you get the default C behavior which is an indeterminate initialization. In order to initialize theint
member variable with a default initialization value, you would have to add it to the initialization list of the structure's constructor. For example,Where-as