我正在对一个(相当老的)C++项目进行 "现代化 "改造,偶然发现了这个部分。
旧的代码是为一个动态数组分配内存,然后在需要的时候为元素调用构造函数。我想为所有元素调用构造函数的成本很高,所以作者选择了这种方式(性能对这个项目至关重要)。旧的代码是这样的(简化了)。
struct my_struct {
my_struct(int x, int y, int z) { /* expensive ctor */ }
};
struct other_class {
my_struct* arr;
other_class(int n) {
arr = (my_struct*) malloc(n * sizeof(arr[0]);
}
void foo(int idx, int a, int b, int c) {
new (&arr[idx]) my_struct(a, b, c);
}
};
我改了 arr
到 std::vector<my_struct>
并使用 std::reserve
来 "保留 "内存。代码工作正常,通过了当前所有的测试,但我知道这是不对的,因为 std::reserve
并不会增加该向量的大小,所以调用 arr.size()
仍然会返回0。这是我的代码。
struct other_class {
std::vector<my_struct> arr;
other_class(int n) {
arr.reserve(n);
}
void foo(int idx, int a, int b, int c) {
new (&arr[idx]) my_struct(a, b, c);
}
};
如何让这段代码又快又安全(假设我不能在我的代码中加入一个默认的ctor my_struct
)? 谢谢你。
编辑:这里是示例代码。它被编译并按预期运行,没有任何警告。http: /cpp.sh8ytwf
但我知道这是不对的,因为std::reserve并没有增加那个向量的大小。
std::reserve
确实增加了向量的容量,当你只想分配内存,只想以后推送元素时,这正是你想要的。
向量所管理的底层数组的大小不等于向量的 size()
. size()
返回容器中的元素数量,只要没有任何的 size() == 0
.
当你以后推送元素时,你不需要使用新的位置,但你应该使用 push_back
或 emplace_back
. 更具体的说,这是错误的,因为你访问的是界外的向量(记住:size是元素数,容量可以更大,但不能访问比向量大小更大的索引)。
new (&arr[idx]) my_struct(a, b, c);
因为你访问的是越界的向量(记住: 大小是元素的数量,容量可以更大,但你不能访问比向量大小更大的索引)。相反的,我们可以用 "向量 "来代替。
arr.emplace_back(a,b,c);
虽然我喜欢 emplace_back
更好的解决方案,你可以使用 std::optional
在这种情况下。这将允许您使用 idx
来决定哪个元素被有效对象填充。
struct other_class
{
std::vector<std::optional<my_struct>> arr;
other_class( int n ) : arr( n, std::nullopt )
{ }
void foo( int idx, int a, int b, int c )
{
arr.at( idx ).emplace( a, b, c );
}
};