我将创建一些具有虚拟副本功能的父类和子类,它返回self的副本:
class A{
public:
int ID;
virtual A* copy(){
return new A();
}
}
class B : public A{
public:
int subID;
virtual A* copy(){
B* b=new B();
memcpy(b,this,sizeof(B));
return b;
}
};
编译时会显示以下警告:
destination for this 'memcpy' call is a pointer to dynamic class 'B' ; vtable pointer will be overwritten
explicitly cast the pointer to silence this warning
这个警告意味着什么,它会导致什么潜在问题?
答案 0 :(得分:7)
这意味着这不起作用。不应该使用C库的memcpy
()函数复制C ++对象(在某些有限的情况下除外),它对C ++类,它们的构造函数,析构函数,虚拟方法以及其他所有内容都不了解。 39;用C ++编写,不在C中。
你想要的是copy constructor。它的工作正是你要完成的工作:制作现有对象的副本。
virtual A* copy(){
B* b=new B(*this);
return b;
}
答案 1 :(得分:0)
当一个类型声明虚拟成员时,这个类型的每个实例(对象)或从它继承的实例必须知道它的真正底层类型。因为如果你需要多态性,那么它可能意味着代码的某些部分不一定有这些信息来做出正确的决定(例如,知道何时调用重写方法而不是基础)。
据我所知,这总是通过在对象的内部存储一个魔术的,自动生成的成员来实现,但不一定强制执行。这个成员可以做很多事情,从而解决所有与多态相关的问题,但它确实是一个你不应该修补的黑盒子。它甚至没有用名字,甚至是界面向你展示。这个特定于实现的魔法被称为vtable。
现在,如果你memcpy到对象的最开头,这是该成员所在的位置,你将使用垃圾无关数据来篡改其值。您将有效地破坏此对象的类型感知,并且一旦使用垃圾vtable,很可能会崩溃。
答案 2 :(得分:0)
这是警告,不是错误。这是有原因的:这取决于您对memcpy()
进行的精确操作,是否会导致动态对象出现任何异常行为。
因此是,通过赋值/构造函数进行的复制比通过虚拟方法在C ++对象上进行的原始内存复制更安全(因此,使用对此表示怀疑),但是否对他们使用memcpy()
不一定总是错误。如果您在它们上正确使用memcpy()
,则不会出现任何不良行为。
详细信息:
使用大多数编译器,动态C ++对象会自动获得称为 vpointer 的附加成员,该成员通常是 由编译器作为对象的第一个成员生成,因此在对象的存储位置的最开始。该vpointer只是一个 pointer ,它是该类的 vtable 所在的内存地址。您可以轻松地检查以下内容:
#include <stdlib.h>
#include <stdint.h>
// Non-dynamic class.
class Foo {
public:
int a;
void asdf() {}
};
// Dynamic class.
class Bar {
public:
int a;
virtual void asdf() {}
};
int main(int argc, char **argv) {
Foo foo;
Bar bar;
printf("&foo=%p &foo.a=%p delta=%lld\n", &foo, &foo.a, uint64_t(&foo.a) - uint64_t(&foo));
printf("&bar=%p &bar.b=%p delta=%lld\n", &bar, &bar.a, uint64_t(&bar.a) - uint64_t(&bar));
return 0;
}
运行此代码时,您会看到a
的成员变量class Foo
具有与对象本身完全相同的内存地址。而a
的成员变量class Bar
的内存地址带有偏移量(例如+8)。这是因为编译器将 vpointer 作为第一个成员变量注入到class Bar
(作为隐藏的成员变量)的对象中。
(只读) vtable 本身(对象的 vpointer 指向的位置)本身由特定动态类的所有对象在每个类级别上共享, vtable用于将虚拟方法名称转换为该虚拟方法的一种特定实现。
因此,当您memcpy()
个动态对象时,必须仔细处理该对象包含vpointer的事实。仅将动态对象数组从一个内存位置复制到另一个内存位置,而实际上完全复制整个对象并保留其类型,通常是安全的。一个常见的用例是通过memcpy()
将它们动态添加到具有更大大小的新malloc()
版的内存块中,然后free()
进行添加,从而有效地增加包含动态对象的容器的大小。旧块。
另一方面,一个有问题的示例是当您尝试将一个动态类的对象memcpy()
转换为另一个动态类的对象时。在这种情况下,您至少会覆盖vpointer并更改类类型,因此在memcpy()
之前和之后可能会执行不同的方法。但是又一次:这是否取决于您的用例,是否属于“不当行为”。您甚至可能打算更改类类型(包括对象到虚拟方法的映射)。
还:从上面的代码示例中可以看到,当您将memcpy()
从动态类的对象转换为非动态类的对象时,反之亦然,您必须了解由于内存布局的不同到注入的vpointer。特别是在此示例中,vpointer的存在与不存在是棘手的。我的意思是,偶尔强制执行C风格的指针类型转换非常普遍,由于这种情况,它可能无法实现您的预期。
另一个问题是兼容性:正式的C ++并没有规定编译器应如何精确地实现动态对象。因此,即使我在此处编写的内容确实适用于所有主要编译器,但正式的C ++甚至不需要存在vtable或vpointer。因此,从理论上讲,编译器可以自由地以完全其他的方式实现动态对象,这可能会对整个问题产生不同的评估。但是,通过为软件编写测试用例就可以很容易地保护这个理论问题。