Question

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?

Was it helpful?

Solution 2

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.

OTHER TIPS

"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.

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

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top