C++ 使用按引用传递而不是按指针传递时的二进制兼容性

C++ 使用按引用传递而不是按指针传递时的二进制兼容性,c++,pass-by-reference,binary-compatibility,pass-by-pointer,interface-design,C++,Pass By Reference,Binary Compatibility,Pass By Pointer,Interface Design,本问题旨在作为本问题的后续问题: 在阅读了关于stackoverflow的答案和进一步的讨论之后,我知道编译器应该像对待指针传递一样对待引用传递,并且引用只不过是语法上的糖分。有一件事我还没有弄清楚,考虑到二进制兼容性是否有任何区别 在我们的(多平台)框架中,我们需要在版本和调试版本之间(以及框架的不同版本之间)保持二进制兼容。特别是,我们在调试模式下构建的二进制文件必须可用于发布版本,反之亦然。 为了实现这一点,我们只在接口中使用纯抽象类和POD 考虑以下代码: class IMediaSer

本问题旨在作为本问题的后续问题:

在阅读了关于stackoverflow的答案和进一步的讨论之后,我知道编译器应该像对待指针传递一样对待引用传递,并且引用只不过是语法上的糖分。有一件事我还没有弄清楚,考虑到二进制兼容性是否有任何区别

在我们的(多平台)框架中,我们需要在版本和调试版本之间(以及框架的不同版本之间)保持二进制兼容。特别是,我们在调试模式下构建的二进制文件必须可用于发布版本,反之亦然。 为了实现这一点,我们只在接口中使用纯抽象类和POD

考虑以下代码:

class IMediaSerializable
{
public:
    virtual tResult Serialize(int flags,
                              ISerializer* pSerializer,
                              IException** __exception_ptr) = 0;
//[…]
};
iseralizer
ieexception
也是纯抽象类<代码>ISerializer必须指向现有对象,因此我们必须始终执行空指针检查
IException
实现某种异常处理,其中指针指向的地址必须更改。由于这个原因,我们使用指针到指针,它也必须是空指针检查

为了使代码更加清晰,并消除一些不必要的运行时检查,我们希望使用passbyreference重写此代码

class IMediaSerializable
{
public:
    virtual tResult Serialize(int flags,
                              ISerializer& pSerializer,
                              IException*& __exception_ptr) = 0;
//[…]
};
这似乎没有任何缺陷。但问题仍然是我们是否仍然满足二进制兼容性的要求

更新: 澄清一下:这个问题不是关于代码的指针传递版本和引用传递版本之间的二进制兼容性。我知道这不可能是二进制兼容的。事实上,我们有机会重新设计我们的API,我们考虑使用PASS参考而不是PASS指针,而不关心二进制兼容性(新的主要版本)。
当只使用引用传递版本的代码时,问题只是关于二进制兼容性。

二进制ABI兼容性由您使用的任何编译器决定。C++标准不涉及二进制ABI兼容性问题。
你需要检查C++编译器的文档,看看它对二进制兼容性的说明。

不,不管你使用哪个编译器,它都不能工作。 考虑导出两个函数的类Foo:

class Foo
{
public:
     void f(int*);
     void f(int&);
};
编译器必须将两个函数的名称
f
转换为ABI特定的字符串,以便链接器能够区分这两个函数

现在,由于编译器需要支持重载解析,即使引用的实现与指针完全相同,这两个函数名也需要有不同的名称

例如,GCC将这些名称更改为:

void Foo::f(int*) => _ZN3Foo1fEPi
void Foo::f(int&) => _ZN3Foo1fERi
注意
p
vs
R


因此,如果您更改函数的签名,您的应用程序将无法链接。

通常,引用是作为隐藏的指针实现的,因此通常存在ABI兼容性。您必须检查特定编译器的文档以及可能的实现,以确保

然而,在C++11时代,您对纯抽象类和POD类型的限制过于热情

C++11将pod的概念分解为多个部分。标准布局涵盖了pod类型的大部分(如果不是全部的话)内存布局保证

但是标准布局类型可以有构造函数和析构函数(除其他区别外)

因此,您可以创建一个非常友好的界面

编写一个简单的智能指针,而不是手动管理的接口指针

template<class T>
struct value_ptr {
  T* raw;
  // ...      
};
最终的结果是,您得到了一个行为类似于常规日常类型的类型,但它是作为纯虚拟接口类的包装器实现的。这些类型可以按值获取、按引用获取和按值返回,并且可以在其中保存任意复杂状态

这些类型存在于库公开的头文件中

IFoo
的接口扩展也很好。只需在类型末尾的
IFoo
中添加一个新方法(在大多数ABI下都是向后兼容的(!)——试试看),然后在转发给它的
my\u regular\u foo
中添加一个新方法。由于我们没有更改
my_regular\u foo
的布局,即使库和客户机代码可能不同意它有哪些方法,这很好--这些方法都是内联编译的,从不导出--知道它们正在使用库的较新版本的客户机可以使用它,那些不知道但正在使用它的人很好(没有重建)

这里有一个小问题:如果你给一个方法的
IFoo
添加一个重载(而不是重写:重载),那么虚拟方法的顺序就会改变,如果你添加一个新的
virtual
父对象,那么虚拟表的布局就会改变,只有在公共API中对抽象类的所有继承都是
virtual
的情况下,这种方法才能可靠地工作(通过虚拟继承,vtable具有指向子类中每个vtable开头的指针:因此每个子类都可以有一个更大的vtable,而不会弄乱其他函数的地址虚拟函数。如果您只小心地将vtable代码附加到子类的末尾,则使用前面的头文件仍然可以找到前面的方法)


最后一步——允许在接口上使用新方法——可能是通向far的桥梁,因为您必须研究ABI保证(在实践中,而不是在实践中)每个受支持的编译器的vtable布局。

即使它是二进制兼容的,函数名也会有不同的损坏,因此它不会链接。澄清了这个问题,我希望问题现在变得清楚了。POD在C++11中消失了(完全分离):现在我们有了标准布局,它允许类中有很多功能。您可以通过regula
struct my_regular_foo {
  value_ptr< IFoo > ptr;
  bool some_method() const { return ptr->some_method(); } // calls pure virtual method in IFoo
};