当从此到子类中的新对象时,将显示警告"destination for this 'memcpy' call is a pointer to dynamic class..."
When memcpy from this to a new object in a child class, warning "destination for this 'memcpy' call is a pointer to dynamic class..." shows
我将使用虚拟复制函数创建一些父类和子类,该函数返回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
此警告是什么意思,它会导致哪些潜在问题?
这意味着这将不起作用。C++对象不应使用 C 库的memcpy
() 函数进行复制(某些有限情况除外),该函数对 C++ 类、它们的构造函数、析构函数、虚拟方法以及 C++ 中没有的 C 中的所有其他内容一无所知。
你想要的是一个复制构造函数。它的工作正是您要完成的:制作现有对象的副本。
virtual A* copy(){
B* b=new B(*this);
return b;
}
这是警告,而不是错误。这是有原因的:这取决于你实际用什么精确地memcpy()
它是否会导致动态对象的任何不当行为。
所以是的,通过赋值/构造函数复制比使用虚拟方法在C++对象上进行原始内存复制更安全(因此在怀疑时使用它),但是对它们使用memcpy()
不一定总是错误。如果你正确地使用memcpy()
,你就不会得到不当行为。
详细地:
对于大多数编译器,动态C++对象会自动获得一个名为vpointer的附加成员,该成员通常由编译器作为对象的第一个成员生成,因此位于对象的内存位置的最开头。vpointer 只是一个指针,即类的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=%lldn", &foo, &foo.a, uint64_t(&foo.a) - uint64_t(&foo));
printf("&bar=%p &bar.b=%p delta=%lldn", &bar, &bar.a, uint64_t(&bar.a) - uint64_t(&bar));
return 0;
}
运行此代码时,您将看到成员变量a
class Foo
与对象本身具有完全相同的内存地址。而class Bar
的成员变量a
具有带有偏移量的内存地址(例如 +8)。这是因为编译器将vpointer作为第一个成员变量注入到class Bar
的对象(作为隐藏成员变量)。
(只读)vtable本身(对象的 vpointer 指向的位置)由特定动态类的所有对象在每个类级别共享,并且vtable用于将虚拟方法名称转换为该虚拟方法的一个特定实现。
因此,当您memcpy()
动态对象时,您必须仔细处理对象包含 vpointer 的事实。只需将动态对象数组从一个内存位置复制到另一个内存位置,同时真正复制整个对象并保留其类型,通常是安全的。一个常见的用例是有效地增加包含动态对象的容器的大小,方法是将它们memcpy()
到具有更大尺寸的新malloc()
内存块,然后free()
旧块。
另一方面,一个有问题的示例是当您尝试将一个动态类中的对象memcpy()
到另一个动态类的对象时。在这种情况下,您至少会覆盖 vpointer 并更改类类型,因此可以在memcpy()
之前和之后执行不同的方法。但再说一遍:这取决于您的用例是否是"不当行为"。您甚至可能打算更改类类型(包括对象到虚拟方法的映射)。
另外:从上面的代码示例中可以看出,当您从动态类的对象memcpy()
到非动态类的对象,反之亦然时,您必须意识到由于注入的 vpointer 而导致的内存布局差异。特别是在这个例子中,vpointer 的存在与不存在是很棘手的。我的意思是,偶尔强制使用 C 样式的指针转换是很常见的,因此可能无法满足您的想法。
另一个问题是兼容性:官方C++并没有规定编译器应该如何实现动态对象。因此,即使我在这里写的东西基本上适用于所有主要的编译器,正式C++甚至不需要vtable或vpointer的存在。因此,从理论上讲,编译器可以自由地以完全不同的方式实现动态对象,这可能会对整个问题进行不同的评估。但是这个理论问题可以通过为您的软件编写测试用例来轻松解决。
当一个类型声明虚拟成员时,这种类型的每个实例(对象)或从它继承的实例(对象)都必须以某种方式知道它真正的底层类型。因为如果您需要多态性,那么这可能意味着您的代码的某些部分不一定具有此信息来做出正确的决策(例如,知道何时调用重写的方法而不是基方法)。
据我所知,这总是通过在你的对象的内部存储一个神奇的自动生成的成员来实现的,但不一定是强制性的。这个成员可以做很多事情,从而解决所有与多态性相关的问题,但它作为一个你不应该修补的黑匣子。它甚至没有向你公开一个名字,甚至没有一个界面。这个特定于实现的魔法称为 vtable。
现在,如果你回到对象的最开头,也就是这个成员所在的位置,你将用垃圾不相关的数据践踏它的值。您将有效地破坏此对象自己的类型感知,并且很可能在使用垃圾vtable后立即崩溃。
- "error: no matching function for call to"构造函数错误
- 表示"accepting anything for this template argument" C++概念的通配符
- 如何在C++中从两个单独的for循环中添加两个数组
- 在Linux for Windows上编译C++代码时出错
- 调用专用模板时出错"no matching function for call to [...]"
- 为什么我的for循环不能正确获取argv
- 为什么我不能在 FOR LOOP 中使用 i/10,C++?
- Arduino:for/while/if在void setup()或void loop()之前?——错误:之前需要不合格
- 在基于范围的for循环中使用结构化绑定声明
- 通过for循环使用用户输入填充列表
- 使用for循环检查数组中的重复项
- 在for循环中使用auto vs decltype(vec.size())来处理字符串的向量
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- 正在使用for循环创建QScatterSerie
- Python中的for循环与C++有何不同
- std::memory_order for std::atomic:<T>:wait
- 在更改for循环的第三部分后,未使用for循环结果
- 在 for 循环中查找问题时遇到困难
- 嵌套for循环C++的问题(初学者)
- 当从此到子类中的新对象时,将显示警告"destination for this 'memcpy' call is a pointer to dynamic class..."