我有一个带有虚函数的 C++ 类。如果我使用
=
将它复制到我有 malloc 的内存中,然后调用虚函数,我会得到一个分段错误。
#include <iostream>
class Greeter {
public:
virtual void print_hi() {
std::cout << "hello" << std::endl;
}
void print_bye() {
std::cout << "bye" << std::endl;
}
};
int main() {
Greeter* greeters = (Greeter*) malloc(sizeof(Greeter) * 16);
Greeter g;
greeters[0] = g;
greeters[0].print_bye(); // prints "bye"
greeters[0].print_hi(); // segmentation fault
}
我期望使用赋值运算符
=
将g
复制到greeters[0]
,并且我的虚函数调用将正常工作。相反,似乎与虚函数指针相关的东西已经被破坏了。不过,常规方法(例如print_bye()
)仍然有效。
如果你好奇为什么:我正在通过存储一个连续的对象数组来优化我的一些数据局部性(缓存)代码,而不是指向独立堆分配的对象的指针数组,并且可能不要紧紧包装。另外,我在 CUDA 中使用了一些代码,这就是为什么我使用
malloc
(或 cudaMalloc
)而不是 std::vector
.
我可以通过以下方式解决这个问题:
Greeter greeters[16]; // instead of the malloc line
Greeter* g = new(&greeters[0]) Greeter();
g->print_hi();
Greeter g;
memcpy(&greeters[0], &g, sizeof(Greeter));
greeters[0].print_hi();
为什么这些方法有效,而赋值运算符却无效?
根本的问题是
greeters
从来没有被正确构造过,因此 不能在以后分配 并且只是假设它会工作。
(特别是 v-table
将包含垃圾。)
这是因为
malloc
不会构造,而new
会。 (如果幸运的话还有其他选择。)
在这种情况下这尤其令人困惑,因为即使类看起来是空的,具有虚函数的类也具有“隐藏”状态并且赋值不需要覆盖此状态,因为该类没有成员。
使用
new
是正确的做法,程序有效。
#include <iostream>
class Greeter {
public:
virtual void print_hi() {
std::cout << "hello" << std::endl;
}
void print_bye() {
std::cout << "bye" << std::endl;
}
};
int main() {
Greeter* greeters = new Greeter[16];
Greeter g;
greeters[0] = g;
greeters[0].print_bye(); // prints "bye"
greeters[0].print_hi();
delete[] greeters;
}
https://godbolt.org/z/ejr4z4f1W
为了进一步说明这一点,尽管使用
malloc
是一个糟糕的想法,但问题是缺乏结构,在这种情况下明确说明:
int main() {
std::allocator<Greeter> alloc;
Greeter* greeters = alloc.allocate(16);
for(auto p = greeters; p != greeters + 16; ++p){
alloc.construct(p); // comment this and you will get a seg fault too
}
Greeter g;
greeters[0] = g;
greeters[0].print_bye(); // prints "bye"
greeters[0].print_hi();
for(auto p = greeters; p != greeters + 16; ++p){
alloc.destroy(p);
}
alloc.deallocate(greeters, 16);
}
https://godbolt.org/z/qW4feqxsj
现在,烧掉那本书!