D指针/Q指针在C++中的使用

最近接触C++动态链接库开发,遇到了动态链接库的二进制兼容问题,首先引入二进制兼容的概念:

A library is binary compatible, if a program linked dynamically to a former version of the library continues running with newer versions of the library without the need to recompile.

大概意思是:如果应用程序在动态链接某library的前后两个版本时不需要重新编译,则称这个library是二进制兼容的。

(另外还有个源码兼容的概念:比二进制兼容稍弱一些,即除了链接某library的前后两个版本时需要重新编译之外,再不需要其他任何改动,则称这个library是源码兼容的。Source compatible)

1. 引入D指针以保证动态链接库代码的二进制兼容性
我们先看一下什么情况下会造成二进制不兼容。

比如我们有个test.dll的动态链接库,它包含一个Foo类和其子类FooC,定义如下:

class TESTDLL_API Foo {  //Foo.h
public:
  Foo();
  double getCurrent();
private:
  double m_current;
}
class TESTDLL_API FooC : public Foo {  //FooC.h
public:
  FooC();
  double getFuture();
private:
  double m_future;
}
将Foo和FooC发布成test.dll后,供应用程序app.exe调用。

此时,需求说要在Foo类中加入一个新的成员变量,m_previous,那么此时Foo类的定义是:

class TESTDLL_API Foo {  //Foo.h
public:
  Foo();
  double getCurrent();
  double getPrevious();
private:
  double m_current;
  double m_previous;
}
子类FooC类的定义不变,再发布成test.dll,供应用程序app.exe调用(假设app.exe没有重新编译),那么对于app.exe来说,FooC类的内存布局中m_future的相对偏移为x,而实际上由于Foo中加入了成员变量m_previous,导致运行时FooC类的内存布局中m_future的相对偏移不再是x了。这样就会造成运行时错误(经实验,成员方法getPrevious()的加入并没有影响内存布局),test.dll不具备二进制兼容性。D指针在这种情况下闪亮登场!

首先需要为Foo类添加一个FooPrivate类来定义原来在Foo类中的成员变量(XXXPrivate是惯用的命名方式):

class FooPrivate {  //Foo_p.h
public:
  FooPricate();
private:
  m_current;
}
然后修改Foo.h为:

class FooPrivate;
class TESTDLL_API Foo {  //Foo.h
public:
  Foo();
private:
  FooPrivate *d_ptr;
}
最后如需要添加新的成员变量,则在FooPrivate类中添加,Foo类只有一个指向FooPrivate的D指针,这样就不会改变子类FooC类的内存布局,就可以保证test.dll的二进制兼容性了。

2. 引入Q指针以实现Private类对父类或者公有类方法的访问
在引入作为D指针的定义类(FooPrvate)后,如遇到FooPrivate访问公有类或者包含D指针的类(Foo)的需求,例如 对成员变量修改后调用父类的方法,这时可以在FooPrivate类中加入一个指向公有类或者Foo.class的q_ptr,称为Q指针,如下:

class FooPrivate {  //Foo_p.h
public:
  FooPricate(Foo *p_foo);
private:
  m_current;
public:
  Foo *q_ptr;
}
class FooCPrivate {  //FooC_p.h
public:
  FooCPrivate(FooC *p_fooc);
private:
  double m_future;
public:
  FooC *q_str;
}
FooPrivate和Foo以及FooCPrivate()和FooC()的构造函数分别为:

FooPrivate::FooPrivate(Foo *p_foo) : q_ptr(p_foo) {  //Foo.cpp
...
}
Foo::Foo():d_ptr(new FooPrivate(this)) {  //Foo.cpp
}
 
FooCPrivate::FooCPrivate(FooC *p_fooc) : q_str(p_fooc) {  //FooC.cpp
...
}
FooC::FooC():d_ptr(new FooCPrivate(this)) {  //FooC.cpp
}
这样就可以通过XXXPrivate类中的q_str调用父类的protect或public方法了。

3. 针对多层继承关系下Q、D指针造成的空间浪费优化
但是这种用法会带来一个问题:在拥有多层继承关系的类中,子类创建一个实例后,会额外创建继承路径上所有private类,造成空间浪费,如QListWidget(此类在继承结构上有6层深度),就会为相应的Private类分配6次空间。

因此需要对其进行优化,优化后子类创建一个实例后,只会创建自身对应的private类对象和根private类对象,具体做法如下:

2.1 所有private类都继承根private类;

2.2 去掉各子类中d_ptr,均使用根private类的对象作为隐式D指针;

2.3 去掉各子类中q_str,均使用根private类的Q指针指向根类(仅保留根private类的q_str);

2.4  根类加入protect构造函数,如Foo(FooPrivate &d),允许子类传入自己的private类来初始化。

优化后的FooPrivate、Foo、FooCPrivate、FooC如下所示(FooC继承Foo,FooCPrivate继承FooPrivate):

class FooPrivate  {  //Foo_p.h
public:
  FooPrivate();
  FooPrivate(Foo *p_foo);
private:
  double m_current;
public:
  Foo *q_ptr;
};
 
class FooCPrivate : public FooPrivate  {  //FooC_p.h
public:
  FooCPrivate();
private:
  double m_future;
};
class FooPrivate;
class TESTDLL_API Foo  {  //Foo.h
public:
  Foo(void);
protected:
  //只有子类会访问以下构造函数
  Foo(FooPrivate &d); // 允许子类通过它们自己的private类来初始化
  FooPrivate *d_ptr;
};
 
class FooCPrivate;
class TESTDLL_API FooC : public Foo  {  //FooC.h
public:
  FooC(void);
protected:
  FooC(FooCPrivate &d);
};

由于所有private类都仅继承根private类,在使用q_str的时候要加上强制类型转换:

#define QPTR(Class) Class *q = static_cast(q_ptr)     //q_str实际指向private对应的类的对象,

                                                                                                   //如FooCPrivate对应的类是FooC

同理,在使用d_str的地方要加上强制类型转换:

#define DPTR(Class) Class##Private *d = static_cast(d_ptr)   //d_str实际指向类对应的private对象

                                                                                                                            //如FooC对应的FooCPrivate对象

这两个宏类似于QT中经常见到的Q_Q宏和Q_D宏。

最后附上源代码,使用vs2012编写,Foo*这些类在smcore项目中,main函数在TestConsole中,按照写作思路分成_NODPTR、_DPTR、_QPTR、_QPTR_OPT这四个宏,分别对应上文进行阐述,可以在vs项目 c++预处理 里面使用上述宏。至于smcore项目中Moon、Orbiter等类跟本文并无关系,可以忽略。貌似无法选择0积分上传资源,有会的同学请在评论里告知,谢谢。

https://download.csdn.net/download/haoxinhaoxin/10277398
————————————————
版权声明:本文为CSDN博主「codeswimmer」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/haoxinhaoxin/article/details/79473930

你可能感兴趣的:(c++,windows,java,Q指针和D指针)