scopchanov scopchanov - 1 year ago 62
C++ Question

D-pointer and composition classes in a shared library

I am creating a shared library in Qt5 C++. To allow future updates preserving the binary compatibility I would like to use the d-pointer technique. However, I don't know how to apply it when there is a composition of classes. The examples I've found, including the one here, explain only the case of class inheritance. My question is

Do I need to make a corresponding private class for each class in the
library (myLib, B and C) or only for the main one (myLib) and how to access them later?

Here is my setup and the desired functionality without the private classes:


#include "B.h"

class myLib;
B *getB(int n);

QList<B *> m_b;


#include "C.h"

class B;
C *getC(int n);
QList<C *> m_c;


class C;
int getVar();
int m_var;

And somewhere in the main app:

myLib *m_lib = new myLib();
int k = m_lib->getB(4)->getC(2)->getVar();

Answer Source

From the linked: The problem "Never change the size of an exported C++ class".
The solution: "The trick is to keep the size of all public classes of a library constant by only storing a single pointer. This pointer points to a private/internal data structure that contains all the data.".

As long as your class is not shown to the consumers of your lib, feel free to go D-pointerless. By "shown to the consumers" I mean "with their full definition made available by declarations in the headers meant to be included by the consumer code". Perhaps the terms public/private are suffering from a 'semantic overload' here, let's use the 'exposed'/'opaque' (see the ** footnote)

In your example, both B and C are exposed, so they must be available "by pointers only".

The same goes about myLib class. What is worse: instances of myLib can be obtained by value, because the constructor is public. This means I can do something like:

myLib libObj;

which will make impossible to have "drop in replacements, no recompilation needed" from the future releases of myLib.

I suggest forcing the consumers go through factory method to obtain instances of myLib (or with a 'singleton'). Something on the line of:

class myLib {
  myLib() {


  static myLib* createInstance() {
    return new myLib();

** As an example "exposed/opaque declaration" - the class B is expose to the library consumer (which will know B-s will have.. ahem... private parts), but about class M the consumer knows only that it exists and the library will provide pointers to it:

file "myLib.hpp"

// M_type is a pointer to a class and that's all you,
// the consumer, need to know about it. You give me an M_type
// and ask specific questions about it and you'll
// get the details or access to data I choose to
// make available to you
typedef class M * M_type; 

// Dear the library consumer, this class is public to you.
// You know I'm keeping a list of M_type, even if you also know
// you'll never get you hands directly on that list, because
// it has a private access. But having this information,
// **you can compute the sizeof(B)**.
class B {

  M_type getM(int n);

  const M_type getM(int n) const;

  // that is one of the "questions" you can ask about an M_type
  const char* getMLabel(const M_type var) const;

  // I'm providing you with access to something that allows
  // you to modify the amount stored by an M_type,
  // even if you don't know (and never will) how
  // I'm storing that amount
  int&        getMAmount(M_type var);

  // You don't need to know how to create M-s, I'll
  // be doing it for you and provide the index of the created
  // M_type. Ask me with getM to get that pointer.
  inr  registerM(const char* label, int amount);

  QList<M_type> ems;

Somewhere, deep inside the library code, there will exist a header that defines what class M is, and myLib.cpp will include it, but that header will be used only to compile the library and never provided with myLib binary releases. As such, class M is opaque (as opposed to exposed) for the library consumer.