为何二进制兼容?

时间:2013-03-16 03:44:41

标签: c++ pimpl-idiom binary-compatibility

我正在学习PIMPL习语。它的一个优点是二进制兼容性。我想知道二进制兼容性的优点是什么。谢谢!

3 个答案:

答案 0 :(得分:3)

它避免了Fragile Binary Interface Problem。它是这样的:

  1. 程序使用库。

  2. 用户升级库。升级会更改库的二进制接口中的内容。

  3. 程序现在在重新编译之前不起作用,因为它是根据旧的二进制接口构建的。

  4. PIMPL习惯的一个优点是它允许您将通常属于类的公共接口的一部分移动到其私有接口中(实际上,移动到私有类的接口中)。您可以在不破坏二进制兼容性的情况下更改专用接口。

答案 1 :(得分:0)

PIMPL习惯用法的优点不在于二进制兼容性,而是在更改实现甚至类的布局时减少了重新编译的需要。例如,如果向类中添加新的数据成员,则会更改类的布局,通常需要重新编译该类的所有客户端,但如果使用PIMPL惯用法则不需要。

二进制兼容性更多的是与多个编译器(和编译器版本)兼容,并且在C ++中执行此操作的唯一方法是使用由未向客户端公开的类实现的接口(抽象类)。这是因为抽象类的vtable布局由所有编译器完全相同地实现。许多API(例如DirectX API)都以这种方式公开,以便它们可以与任何编译器一起使用。

答案 2 :(得分:0)

v1.0

让我们考虑libMagic库v1.0中的以下类

//MagicNumber.h
struct MagicNumber {
  MagicNumber();
  int get();
  int id;
}
//MagicNumber.cpp
int MagicNumber::get() {
  return 42;
}

应用代码:

void foo() {
  MagicNumber m;
  int i = 27;
  std::cout << m.get() + i << '\n';
}

通过动态链接libMagic.so编译上述应用程序代码时,foo函数的编译如下

foo:
   Allocate 4 bytes space in stack for m
   Allocate 4 bytes space in stack for i and write 27 in it
   Call MagicNumber::get //This address is resolved on application startup.
   ... //Rest of processing

v1.0.1

现在,当libMagic发行新版本v1.0.1时,实现方式进行了以下更改,但头文件没有更改

//MagicNumber.cpp
int MagicNumber::get() {
  return call_real_magic_number_fn();
}

应用程序不必重新编译,因此无需更新。它将使用新的实现自动调用更新的版本。


v1.1.0 -二进制不兼容

可以说,该库(v1.1.0)进行了另一次更新,但有以下更改。

//MagicNumber.h
struct MagicNumber {
  MagicNumber();
  int get();
  int id;
  int cache; //Note: New member
}
//MagicNumber.cpp
int MagicNumber::get() {
  if(cache != 0) return cache;
  cache = call_real_magic_number_fn();
  return cache;
}

现在,已编译的foo函数将不会为添加的新成员分配空间。该库具有破坏性的二进制兼容性。

foo:
   Allocate 4 bytes space in stack for m //4 bytes is not enough for m
   Allocate 4 bytes space in stack for i and write 27 in it.
   Call MagicNumber::get //This address is resolved on application startup.
   ... //Rest of processing

会发生未定义的行为。 i=27可能会写在缓存变量上,而MagicNumber::get将返回27。但是任何事情都会发生。


如果libMagic使用了PIMPL惯用语,则所有成员变量将属于MagicNumberImpl类,其大小不会暴露给应用程序代码。因此,库作者可以在更高版本的库中添加新成员,而不会破坏二进制兼容性。

struct MagicNumberImpl;
struct MagicNumber {
  MagicNumber();
  private:
    MagicNumberImpl* impl;
}

以上类定义在新版本中不会更改,并且将新成员添加到类时指针的大小也不会更改。

注意: 仅在以下情况下才需要二进制兼容性

  1. 使用动态链接(例如Linux中的.so文件)链接该库。
  2. 该库已更新为新版本,而无需重新编译应用程序代码。如果库和二进制文件在同一个项目中-您的构建系统将重新编译并自动更新两者。因此,无需为此或PIMPL烦恼。

注2:还有一种不使用PIMPL即可解决相同问题的方法-名称空间的ABI版本控制。