1

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:

myLib.h

#include "B.h"

class myLib;
{
public:
        myLib();
        B *getB(int n);

private:
        QList<B *> m_b;
}

B.h

#include "C.h"

class B;
{
public:
        B();
        C *getC(int n);
private:
        QList<C *> m_c;
}

C.h

class C;
{
public:
        C();
        int getVar();
private:
        int m_var;
}

And somewhere in the main app:

myLib *m_lib = new myLib();
int k = m_lib->getB(4)->getC(2)->getVar();
scopchanov
  • 6,933
  • 10
  • 30
  • 56
  • 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." – Adrian Colomitchi Aug 29 '16 at 06:04

1 Answers1

1

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;
libObj.getB(4)->getC(2)->getVar();

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 {
private:
  myLib() {
  }

public:

  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 {
public:
  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);


private:
  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.

Adrian Colomitchi
  • 3,884
  • 1
  • 12
  • 23
  • Thank You for the answer, Adrian! Let me check if I got that right. If a class is exposed in some way to the library's user, whether directly (by manual creating instances of it) or indirectly (as in my case, by querying the instances created by the main library class) it is public, i.e. exported, hence should be d-pointerified, so to say, to ensure its size does not change in the future releases. Is that right? Unfortunately, I did not get the part starting with **But**. And one additional question. Do only the private attributes of a class go into the private structure, or the methods too? – scopchanov Aug 29 '16 at 08:21
  • @scopchanov "to ensure its size does not change in the future releases. Is that right?" To be more precise, "To ensure that the consumer code is not affected by the size modifications in the future releases". I'll address the other questions in separate comments. – Adrian Colomitchi Aug 29 '16 at 09:09
  • @scopchanov "And one additional question. Do only the private attributes of a class go into the private structure, or the methods too?" The "private/protected/public" standard access qualifiers have no bearing in the matter. When we speak about "public data structures exported by a library" we mean "anything that the consumer of the library gets to know about classes/structs by including the headers made available by that library" - should we call them "exported"? This being said, I'll edit my answer to clarify the conditions in which the size of the exported structures change. – Adrian Colomitchi Aug 29 '16 at 09:23
  • @scopchanov Is it clearer now? If it is "Thank You for the answer, Adrian!" is appreciated, as will be an upvote/accepted answer. – Adrian Colomitchi Aug 29 '16 at 10:20
  • What You wrote is really good and interesting explanaition. Though, I need some time to fully understand it. I have not written libraries before, only code which uses a library, and I need to get used to the concepts of exposing/hiding things to/from the user. Thank You very much for the help! – scopchanov Aug 29 '16 at 10:30
  • The answer is accepted. I upvoted it, but my reputation is still low and it is not visible. :) Again - it was extremly good explained and helpful to me and I appreciate it! – scopchanov Aug 29 '16 at 10:34
  • @scopchanov "It was extremly good explained and helpful to me and I appreciate it!" . I'm happy that it helped you. "I need to get used to the concepts of exposing/hiding things to/from the user." It's quite simple - start by writing simple C (not C++) structs which you expose only by pointers and sets of functions declared to accept pointers to those structs. The structure declarations will be made in a header that won't be distributed with the library binaries, all the "exposed" functions (declared in a header distributed with the lib) work only with pointers to those structs. – Adrian Colomitchi Aug 29 '16 at 11:19
  • Isn't getB and getC in my example exactly that? The actual variable int m_var is burried deep in the class hierarchy and to get to there you use getter after getter. – scopchanov Aug 29 '16 at 11:54