我有一个接口(interface),我在一个共享库中有一个该接口(interface)的实现,谁知道它是用什么编译的。
dynamic_cast
仅适用于多态类型。所以我可以假设它不使用 RTTI,所以我禁用它,然后编译。
而且由于没有标准化的多态性方法,多态类型对象的布局是未知的,其 vtable 的布局也是如此。
因此出现了一个问题:我可以使用共享库中的多态类型的对象,该共享库是使用与我使用的编译器不同的编译器编译的吗?更重要的是,为什么?
最佳答案
C++ 没有标准化的 ABI,但如果您的接口(interface)遵循一些规则,您可以实现大多数编译器之间的 ABI 兼容性——尤其是在 Windows 上。这种技术在 Microsoft 的 COM ( https://en.wikipedia.org/wiki/Component_Object_Model ) 中最为著名,但我也在 Steinberg 的 VST 模型架构 ( https://steinbergmedia.github.io/vst3_doc/base/index.html ) 和“openvr”库 ( https://github.com/ValveSoftware/openvr/blob/5aa6c5f0f6520c59c4dce124541ecc62604fd7a5/headers/openvr.h#L1940 ) 中看到了它。
以下是此类 ABI 兼容 C++ 接口(interface)的基本规则:
- 只有纯虚拟方法。这使得多重继承相对不成问题。一个重要的警告是,只允许实现方执行从一个接口(interface)到另一个接口(interface)的
static_cast
(以保证正确的this
指针调整)。为了解决这个问题,每个COM接口(interface)都提供了QueryInterface()
方法,有点类似于dynamic_cast
。 - 没有虚拟析构函数。一些编译器生成超过 1 个 vtable 条目(参见 Why do I have two destructor implementations in my assembly output? )。这意味着您必须实现自己的机制来从基指针中析构对象。 COM 和 VST3 通过
addRef()
和release()
使用引用计数,并且它们有自己的客户端智能指针类型。或者,您可以使用一个简单的destroy()
方法并将您的对象实例存储在常规std::unique_ptr
或std::shared_ptr
中自定义删除器。 - 内存管理不得跨越接口(interface)边界,即您不得在一侧分配内存并在另一侧解除分配,因为每一侧都可能使用不同的运行时。该库必须提供一个自由函数或接口(interface)方法来释放对象。它还可能允许用户传递分配器,因此内存管理保留在客户端。
- 没有重载的虚拟方法。大多数编译器通常按照声明的顺序将方法放在 vtable 中,但显然 vtable 中重载方法的顺序因编译器而异。
- 所有方法参数必须是原始类型或具有稳定对象布局(最好是 POD)的公共(public)类。您不得使用 C++ 标准库中的任何类,例如
std::string
,因为实现不稳定。 - 界面一旦发布,就绝不能更改。您可以通过继承现有接口(interface)来扩展现有接口(interface):
class IFoo {
public:
virtual void foo() = 0;
};
class IFooEx : public IFoo {
public:
virtual void bar() = 0;
};
或者添加一个新的接口(interface)并使用多重继承。
话虽这么说,作为库作者,您在选择此技术之前应该三思而后行,特别是如果您计划为其他语言添加绑定(bind)。尽管这种 C++ 接口(interface)的 vtables 可以转换为函数指针的 C 结构,但通常的方法是
a) 从 C API 开始并提供客户端 C++ 包装器
b) 使用常规 C++ 接口(interface)并在其上提供 C 层。
但是通过正确的抽象,类 COM 的 C++ 接口(interface)可以很好地用于编程。
关于c++ - 使用共享库中的多态对象安全吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57999911/