二级制兼容
二进制兼容ABI(application binary interface)主要指动态库文件单独升级,现有用到老动态库的应用程序是否受到影响。
二进制兼容:
1 升级库文件,不影响使用库文件的程序。
2 新库必然有新头文件,但是旧的二进制可执行文件还是按照旧的头文件中的“使用说明”来调用库。
UseSharedLibrary.exe SharedLibrary.dll
二进制不兼容示例
1 类的普通成员函数 void f( int ) 改成了 void f( double ) 。老EXE会传int进来,新库会用double的长度取数据。从而发生undefined symbol
2 基类增加虚函数会导致基类虚表发生变化。老EXE调用虚表的时候给出的slot是老的,但是新库里面的这个slot已经是另一个函数了。
3 给函数增加默认参数
4 增加默认模板类型
5 改变enum的值
6 给class Bar增加数据成员导致sizeof(Bar)的值变大
7 如果EXE里调用new Bar,导致new出来的内存盛不下新的Bar对象(构造函数会使用新DLL中的构造函数来填充数据),从而:
1)如果新的库实现访问了新的数据成员肯定会访问到一个无法预知的地方;
2)如果EXE得到的是shared_ptr<Bar> 由DLL来管理内存,那么此时是安全的。
3)如果EXE调用的是p->member 那么肯定不对,因为偏移量可能因为member前面插入了新的成员而被新DLL中构造函数填充了新的成员,从而访问的并不是老的member。
4)如果EXE是使用p->get_member()来获取数据,那么是正常的。
5) 如果p->get_member()是inline的,那么是不安全的,因为偏移量已经在EXE中了。
8 虚函数做接口的基本上都是二进制不兼容的。
二进制安全的场景:
1 增加新的class(定义在新DLL中,老的EXE里没有)
2 增加非virtual函数(定义在新DLL中,老的EXE里没有)
3 增加static成员函数(定义在新DLL中,老的EXE里没有)
解决办法之pimpl技法:
1 头文件只暴露非virtual函数,class的大小固定为sizeof(Impl*)
//头文件
class Graphics
{
public:
Graphics();
~Graphics();//不能是虚函数
void f1(void);
void f2(int);
void f3(int, int);//增加成员函数可以直接添加非virtual函数,不影响二进制兼容
private:
class Impl;//头文件只放声明,sizeof(Graphics)不会变化,成员变量的扩充在Impl中添加
boost::scoped_ptr<Impl> m_impl;//sizeof(Graphics) == sizeof(Graphics::Impl*)
}
//库的源文件
Graphics::Graphics()
:m_imp(new Impl)
{
}
Graphics::~Graphics()
{
}
void Graphics::f1(void)
{
m_impl->f1();//调用转发
}
继承和虚函数是万恶之源
1 继承体系一旦形成,调整起来就开始费劲。
2 Go语言没有继承
C++接口的终极方案
设计模式(四)std::function接口编程彻底取代抽象工厂和工厂方法
https://blog.csdn.net/calmreason/article/details/50903729