11

Is it somehow possible, to accomplish the following:

x.hpp - this file is included by many other classes

class x_impl; //forward declare
class x {
    public:
        //methods...
    private:
        x_impl* impl_;
};

x.cpp - the implementation

#include <conrete_x>
typedef concrete_x x_impl;    //obviously this doesn't work
//implementation of methods...

So basically, I want the users to include the file x.hpp, but be unaware of the conrete_x.hpp header.

Since I can use concrete_x only by a pointer and it appears only as a private data member, a forward declaration should be enough for the compiler to know how much space to prepare for it. It looks quite like the well-known "pimpl idiom".

Can you help me with this?

PS. I don't want to use a void* and cast it around..

emesx
  • 12,555
  • 10
  • 58
  • 91

4 Answers4

9

Actually, it's even possible to completely hide from the user:

// Foo.hpp
class Foo {
public:

    //...

private:
    struct Impl;
    Impl* _impl;
};

// Foo.cpp
struct Foo::Impl {
    // stuff
};

I would just like to remind you that:

  • you will need to write a proper destructor
  • and thus you will also need a proper copy constructor, copy assignment operator, move constructor and move assignment operator

There are ways to automate PIMPL, at the cost of some black magic (similar to what std::shared_ptr does).

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Please take a look at `@Bart van Ingen Schenau`'s idea. What is your solution better at? – emesx Nov 06 '12 at 18:59
  • 1
    @elmes: The name `Impl` is completely hidden, whereas Bart's solution introduces a `x_impl` name in the enclosing namespace. It's just even more hidden with a `private` nested structure. – Matthieu M. Nov 07 '12 at 07:29
  • Would this work if the struct is inside a namespace in cpp file? – ChaoSXDemon May 25 '17 at 00:06
  • @ChaoSXDemon: Any kind of forward declaration works, because you do not need a complete type to form a pointer. Declaring the `struct` as private is merely reinforcing that nobody else should care about it. – Matthieu M. May 25 '17 at 06:31
  • @MatthieuM. Why using `struct` instead of `class` I've search about its use for hiding implementation and can't find out why struct is used... Thanks! – ForeverLearning Jun 08 '22 at 19:19
  • @ForeverLearning: The only difference between `struct` and `class` is that a `struct` default access is `public`, while a `class` default access is `private`. For the PIMPL pattern above, since `Foo` will need to access the members of `Foo::Impl`, it's less typing to use `struct`... that's all. – Matthieu M. Jun 09 '22 at 09:58
5

As an alternative to the answer from @Angew, if the name concrete_x should not be made known to users of class x, you could do this:

in x.hpp

class x_impl;
class x {
  public:
    x();
    ~x();
    //methods...
  private:
    x_impl* impl_;
};

in x.cpp

#include <concrete_x>
class x_impl : public concrete_x { };

x:x() : impl_(new x_impl) {}
x:~x() { delete impl_; }
jww
  • 97,681
  • 90
  • 411
  • 885
Bart van Ingen Schenau
  • 15,488
  • 4
  • 32
  • 41
  • Yes, this is THE solution. It's not universal in C++11 though, since `concrete_x` could be final. One final question: what is the performance / memory cost of deriving the class just to hide it? – emesx Nov 06 '12 at 19:52
  • Compared to directly storing a pointer to concrete_x, there is no performance / memory cost. Just a (small) maintenance cost in that the maintainer needs to understand it. – Bart van Ingen Schenau Nov 06 '12 at 20:54
  • Could you explain what's going on with `concrete_x`? The exemplary header is missing, and its not clear to me why the include is present in `x.cpp` versus adding the class definition of `concrete_x` to the top of `x.cpp`. – jww May 16 '17 at 19:39
  • @jww: In the question, `` is a existing header that declares the class `concrete_x`, which must not be included/known outside of `x.cpp`. That is why I am including the header, rather than declaring the `concrete_x` class directly in the `x.cpp` file. – Bart van Ingen Schenau May 17 '17 at 07:10
2

This will only work when the forward declaration declares the actual name of the class. So either change x.hpp to:

class concrete_x;
class x {
    public:
        //methods...
    private:
        concrete_x* impl_;
};

or use the name x_impl for the class defined in the header <concrete_x>.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
0

That's what interfaces are for. Define an interface (pure virtual class) in your shared header file and give it to users. Inherit your concrete class from the interface and put it in the non-shared header file. Implement the concrete class in the cpp file (you can even define the concrete class inside the cpp).

SomeWittyUsername
  • 18,025
  • 3
  • 42
  • 85