1

I want to avoid recompilation of everything that includes a public header file, just because something changed in the private part of a class definition. I'm investigating other options beside PIMPL.

This is what I tried:

I created a library that contains a class A:

A_p.h contains private part of class A

void PrivateMethod(int i);

A.h the public header file:

class A
{
public:
    A();
    virtual ~A();
    // other public members
private:
#ifdef A_PRIVATE
#include "A_p.h"
#endif
};

A.cpp

#define A_PRIVATE
#include "A.h"

A::A() {}
A::~A() {}
void A::PrivateMethod(int i) { }

I then created an Win32 console project that includes the public header (A.h) and links against the .lib file.

Everything seems to work, but I'm wondering for any pitfalls along the way. Can anyone elaborate on this?

huysentruitw
  • 27,376
  • 9
  • 90
  • 133
  • 6
    " I read about PIMPL, but am looking for a way to avoid it." why? – Luchian Grigore Apr 10 '13 at 15:46
  • 3
    You are violating the ODR and causing UB. – PlasmaHH Apr 10 '13 at 15:47
  • So what happens if you change the private part? – juanchopanza Apr 10 '13 at 15:49
  • 1
    PIMPL is the correct solution to your problem why create new idioms when well know and understood solutions exist. Other developers will see your code and know whats going on with PIMPL where the next guy who see your code using your solution will just be confused. – rerun Apr 10 '13 at 15:50
  • @LuchianGrigore: Well 'avoid' is a big word here, let's say we're checking out possible alternatives. – huysentruitw Apr 10 '13 at 15:50
  • @WouterHuysentruit The most realistic *third option* for you is to **refactor** your code so your class declaration that's getting so many edits isn't a dependency for so much of your code. It smells like a very large class. – Drew Dormann Apr 10 '13 at 15:59
  • Based on [this](http://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++) information it is possible but there are a lot of rules and limitations. – huysentruitw Apr 10 '13 at 17:31

4 Answers4

6

"Everything seems to work" - The seems there is essential. You're just experiencing undefined behavior. That's an illformed program - a class definition must be identical across compilation units that use that class.

Since this is UB, it can appear to work, but try declaring a virtual method in the private section and you'll most likely experience some visible issues.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • Isn't there any way to ensure binary compatibility? – huysentruitw Apr 10 '13 at 15:48
  • 3
    @WouterHuysentruit no, because this approach is fundamentally wrong. – Luchian Grigore Apr 10 '13 at 15:48
  • 4
    @WouterHuysentruit plus, you **should** use the PIMPL idiom - this right here is one of the reasons it exists in the first place. – Luchian Grigore Apr 10 '13 at 15:49
  • I would like to use Qt as example. A Qt based program compiled against the 4.8.3 headers will still work on a system with Qt 4.8.4 Libraries. And they do add new methods to existing classes. But in some way they seem to preserve binary compatibility... – huysentruitw Apr 10 '13 at 16:06
  • @LuchianGrigore IMHO, PIMPL has one drawback -- heap allocation. Small objects allocated on stack cause heap allocation. That's why `optional` not just a value-semantics pointer. There are approaches like fast-pimpl, but it obviously not good at all =( – kassak Apr 10 '13 at 16:08
  • @LuchianGrigore that was about your **should** – kassak Apr 10 '13 at 16:08
  • @WouterHuysentruit when they change the version, they also change the header, right? Not familiar with Qt, but I'm sure they don't do this, because it's not correct. – Luchian Grigore Apr 10 '13 at 16:12
  • Yes they change the header, but the program binary doesn't get recompiled against the new header and it still works. – huysentruitw Apr 10 '13 at 16:15
  • Thinking about Qt, I just found a great article: http://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++ It seems possible but there are rules and limitations. – huysentruitw Apr 10 '13 at 17:08
1

Abstract classes permit you to declare a public interface but have private data and functions, by subclassing the abstract class.

A key reason this will not work the way you describe, and therefore is not supported in the C++ standard, is that your proposed public declaration makes it impossible to know the size of A. The public declaration does not reveal how much space is needed for the private data. Therefore, code that sees only the public declaration cannot perform new A, cannot allocate space for a definition of array of A, and cannot do arithmetic with pointers to A.

There are other issues, which might be worked out in some way, such as where pointers to virtual function members are located. However, this causes needless complications.

To make an abstract class, you declare at least one virtual function in the class. A virtual function is defined with = 0 instead of a function body. This says it has no implementation, and therefore there can be no object of the abstract class except as a sub-object of a class derived from it.

Then, in separate, private code, you declare and define a class B that is derived from A. You will need to provide ways to create and destroy objects, likely with a public “new” function that returns a pointer to A and works by calling a private function that can see the declaration of B and a public “delete” function that takes a pointer to A and works by calling a private function that can see the declaration of B.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Now, that is a good reason not to use this construction. The size of the class will indeed be different in lib and exe. – huysentruitw Apr 10 '13 at 16:29
1

There are 3 good ways of hiding this sort of information:

  1. only forward-declare your class. This only works if it's just passed around through the client code (through pointers and/or references), and only used inside your library. Your library will need to provide factory functions or similar to return the pointer/reference in the first place, the client can never call new or delete

  2. expose an abstract base class, and again provide factory functions (which instantiate a concrete derived class visible only inside your library)

  3. use pimpl

I'd also agree that you should reconsider any class so large that hiding it is necessary, but if you really can't break it up, those are your options.


As for how & why the ODR violation breaks things in practise:

  • the library and its client code can have different opinions about the size of instances. This can cause problems with allocation/deallocation etc.
  • they can also expect different data member offsets, vtable layouts, etc.
    • PS. inlined methods count as client code for these purposes, since they're not updated by dropping in a new dynamic library build
    • PPS. newer optimizers may be able to inline some out-of-line methods without you knowing
Useless
  • 64,155
  • 6
  • 88
  • 132
0

As written, a proper partical class is not possible in C++, and in some cases this is actually annoying. Inheritance and Pimpl patterns offer an alternative, but with the overhead of one pointer for each object, which can be high in embedded software where RAM is limited.

To solve the issue there was an official "partial class" proposal to the ISO:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0309r0.pdf

Unfortunately the proposal was rejected.

db2000
  • 31
  • 1