我有一个 C++ 标头声明一个仅由纯虚拟方法组成的类。我有两个使用该头文件的 DLL(其中一个实现了该接口),但在编译时没有链接。一个 DLL 动态加载另一个,将已实现接口的指针传递给另一个。这些 DLL 是否共享相同的虚拟表结构?
4 回答
当然,类头足以构建完整的类(这里说的是内存中的布局,一切是如何定位的,而不是其中的实际数据),包括精确的虚拟表结构。
想想看,每个链接对象(你的 .cpp 文件)都是单独编译的,只有头文件是共同的,但是在编译时编译器必须知道虚拟表的精确结构才能正确路由虚拟调用。
顺便说一句,有一个问题要通读……
如果我理解正确,你会得到这样的东西:
啊
class A
{
public:
virtual void foo()=0;
}
B.cpp
#include <A.h>
class B : public A
{
public:
void foo()
{}
}
C.cpp
#include <A.h>
void bar(A * a)
{}
所以B.cpp
有一个实现 的类A
,并且C.cpp
有一些函数接受一个指向实例的指针A
(你提供了一个实例B
)。那是对的吗?
如果是这样,那么是的,它们共享一个 vtable。vtable 是通过编译 class 创建的B
,C.cpp
根本没有任何自己的 vtable。
你安全了。
方法出现在 中的顺序vftable
由基类结构决定,这就是您应该关心的全部。但这是特定于编译器的,因此请使用相同的编译器来生成 dll。不要依赖它们向后兼容(或至少检查文档)。
假设您有以下标题:
//header.h
class A
{
public:
virtual void foo() = 0;
virtual void goo() = 0;
};
你有B.dll
以下课程:
class B : public A
{
public:
virtual void foo() {}
virtual void goo() {}
}
现在,在 中X.dll
,您会收到一个指向在A
中创建的B
对象的指针B.dll
。
通话
void test( A* a )
{
a->foo();
}
将调用B::foo()
.
您可以尝试的一个巧妙的实验是编译B.dll
,header.h
当您编译时X.dll
,颠倒以下方法的顺序header.h
:
//header.h
class A
{
public:
virtual void goo() = 0;
virtual void foo() = 0;
};
在这种情况下,尽管您永远不应该这样做,但对test()
in的相同调用X.dll
可能会调用 method B::goo()
。那是因为X.dll
假定vftable
标题中的存在。这是未定义的行为;我只是写了这个例子来说明一点。
这完全取决于编译器,但一般来说,当一个类是纯虚拟的并且没有在翻译单元中定义任何成员函数时,编译器会将 生vtable
成为弱符号。在您的特定情况下,不同的翻译单元将为基本类型生成单独的完全相等vtable
的 s ,并且链接器/加载器将丢弃除一个符号之外的所有符号,就像使用任何其他弱符号一样(想想模板化的非内联函数) .
但是请注意,生成vtable
不是标准化的,这意味着如果您混合来自两个不同编译器甚至版本的代码,您可能会导致 ODR 违规。