库的二进制兼容
对于一个已经发布的库,如果在库的某个接口类中增加了一个成员,并重新发布该库,如果使用该库的程序直接替换该库后运行时会导致程序崩溃,解决办法就是重新编译应用程序,这是因为,新库中某些接口类及继承于它的类内存布局都已变化,但是旧的应用程序是根据之前的类内存布局,在编译期间分配好了内存,如果不重新编译程序,那么就会导致访问一个数据时,其可能已被覆盖,从而导致崩溃。
所以二进制兼容是指如果自己的程序使用了第三方模块,二进制兼容可以保证在第三方模块修改之后,自己的程序可以不用重新编译就能够兼容修改后的第三方模块。
实现方法
它其实就是pimpl设计模式的具体应用。在了解二进制兼容实现方法前,引入两个概念:主类和私有类。我把二进制库提供给第三方程序的接口类称为主类,把二进制程序内部使用的类称作私有类。只要不修改主类的成员,无论怎么修改二进制库,都可以做到二进制兼容。为直观说明二进制兼容的优势,举个例子,假如我们写了一个库,提供了如下的接口:
class Interface{
public:
Interface(){m_greeting="hello\n";}
void say(){
std::cout<<m_greeting;
}
private:
std::string m_greeting;
}
接下来我们因为新的需求,需要对库进行修改,修改如下:
class Interface{
public:
Interface(){m_greeting="hello\n";m_name="mike";}
void say(){
std::cout<<m_greeting+m_name;
}
private:
std::string m_greeting;
std::string m_name;
}
这时候,由于主类成员发生了修改,我们不仅要重新编译库,还要重新编译所有基于这个库的程序。
接下来我们对上述代码进行重构,修改成二进制兼容模式,二进制兼容的实现方式类似于设计模式中的Pimpl,关键点就是在主类中定义一个指向私有类的指针,简单实现方式如下:
//导出类 interface.h
class Interface{
public:
Interface(){m_ptr = new ClassPrivate;}
~Interface(){delete m_ptr;}
void say(){
if(m_ptr!=nullptr){m_ptr->say();}
}
private:
ClassPrivate* m_ptr;
}
//私有类 classprivate.h
class ClassPrivate{
public:
ClassPrivate(){m_greeting="hello\n";}
void say(){std::cout<<m_greeting;}
private:
std::string m_greeting;
}
当需求发生变动,我们只需要作出如下修改:
class ClassPrivate{
public:
ClassPrivate(){m_greeting="hello\n";m_name="mike";}
void say(){std::cout<<m_greeting+m_name;}
private:
std::string m_greeting;
std::string m_name;
}
因为我们没有修改主类Interface,只是对私有类进行了修改,因此不需要重新编译基于该库的程序,缩短了编译流程。
好处
私有的结构体可以随意改变,而不需要重新编译整个工程项目
隐藏实现细节
头文件中没有任何实现细节,可以作为API使用
原本在头文件的实现部分转移到乐源文件,所以编译速度有所提高
Qt中Q和D指针实现
- d指针是在主类中使用的,主类获取私有类或类中私有变量的指针
- q指针是在私有数据类中使用的,来获取主类对象指针
Q_DECLARE_PRIVATE宏和Q_D宏配合用来在主类中访问私有类对象
#define Q_DECLARE_PRIVATE(Class) \
inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
friend class Class##Private;
#define Q_D(Class) Class##Private * const d = d_func()
Q_DECLARE_PUBLIC宏和Q_Q宏配合用来在私有类中访问主类对象
Q_DECLARE_PUBLIC宏和Q_Q宏
#define Q_DECLARE_PUBLIC(Class) \
inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
inline const Class* q_func() const { return static_cast<const class*>(q_ptr); } \
friend class Class;
#define Q_Q(Class) Class * const q = q_func()
如下为一个完整例子:
// myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QtCore/QObject>
class MyClassPrivate;
class MyClass: public QObject
{
Q_OBJECT
public:
MyClass(QObject *parent = 0);
virtual ~MyClass();
void dummyFunc();
private:
MyClassPrivate * const d_ptr;
Q_DECLARE_PRIVATE(MyClass);
};
#endif // MYCLASS_H
// myclass.cpp
#include "myclass.h"
class MyClassPrivate
{
public:
MyClassPrivate(MyClass *parent)
: q_ptr(parent)
{
}
void foobar()
{
Q_Q(MyClass);
emit q->dummySignal();
}
private:
MyClass * const q_ptr;
Q_DECLARE_PUBLIC(MyClass);
};
MyClass::MyClass(QObject *parent)
: QObject(parent)
, d_ptr(new MyClassPrivate(this))
{
}
MyClass::~MyClass()
{
Q_D(MyClass);
delete d;
}
void MyClass::dummyFunc()
{
Q_D(MyClass);
d->foobar();
}