Search code examples
c++includeapi-design

Can I provide an incomplete header for a C++ class to hide the implementation details?


I would like to split a class implementation into three parts, to avoid that users need to deal with the implementation details, e.g., the libaries that I use to implement the functionality:

impl.cpp

#include <api.h>
#include <impl.h>
Class::Class() {
    init();
}
Class::init() {
    myData = SomeLibrary::Type(42);
}
Class::doSomething() {
    myData.doSomething();
}

impl.h

#include <somelibrary.h>
class Class {
public:
    Class();
    init();
    doSomething();
private:
    SomeLibary::Type myData;
}

api.h

class Class {
    Class();
    doSomething();
}

The problem is, that I am not allowed to redefine headers for the class definition. This does not work when I define Class() and doSomething() only in api.h, either.


A possible option is to define api.h and do not use it in the project at all, but install it (and do not install impl.h).

The obvious drawback is, that I need to make sure, that the common methods in api.h and impl.h always have the same signature, otherwise programs using the library will get linker errors, that I cannot predict when compiling the library.

But would this approach work at all, or will I get other problems (e.g. wrong pointers to class members or similar issues), because the obj file does not match the header?


Solution

  • The short answer is "No!"

    The reason: any/all 'client' projects that need to use your Class class have to have the full declaration of that class, in order that the compiler can properly determine such things as offsets for member variables.

    The use of private members is fine - client programs won't be able to change them - as is your current implementation, where only the briefest outlines of member functions are provided in the header, with all actual definitions in your (private) source file.

    A possible way around this is to declare a pointer to a nested class in Class, where this nested class is simply declared in the shared header: class NestedClass and then you can do what you like with that nested class pointer in your implementation. You would generally make the nested class pointer a private member; also, as its definition is not given in the shared header, any attempt by a 'client' project to access that class (other than as a pointer) will be a compiler error.

    Here's a possible code breakdown (maybe not error-free, yet, as it's a quick type-up):

    // impl.h
    struct MyInternal; // An 'opaque' structure - the definition is For Your Eyes Only
    class Class {
    public:
        Class();
        init();
        doSomething();
    private:
        MyInternal* hidden; // CLient never needs to access this! Compiler error if attempted.
    }
    
    // impl.cpp
    #include <api.h>
    #include <impl.h>
    
    struct MyInternal {
        SomeLibrary::Type myData;
    };
    
    Class::Class() {
        init();
    }
    Class::init() {
        hidden = new MyInternal; // MUCH BETTER TO USE unique_ptr, or some other STL.
        hidden->myData = SomeLibrary::Type(42);
    }
    Class::doSomething() {
        hidden->myData.doSomething();
    }
    

    NOTE: As I hinted in a code comment, it would be better code to use std::unique_ptr<MyInternal> hidden. However, this would require you to give explicit definitions in your Class for the destructor, assignment operator and others (move operator? copy constructor?), as these will need access to the full definition of the MyInternal struct.