Hide implementation by using a pointer (Pimpl idiom)
Asked Answered
L

4

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

Larcenous answered 6/11, 2012 at 17:51 Comment(6)
why can't you have concrete_x inherit from x_impl?Tumular
What exactly is your problem?Beare
Why don't you want to define class x_impl itself in <concrete_x>?Dagny
concrete_x is a library class that I am trying to hideLarcenous
Mandatory readingPentateuch
Could always do something handy with a macro #define x_impl conrete_x #include <x.hpp> (ducks)Unmoved
D
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).

Dowse answered 6/11, 2012 at 18:53 Comment(6)
Please take a look at @Bart van Ingen Schenau's idea. What is your solution better at?Larcenous
@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.Dowse
Would this work if the struct is inside a namespace in cpp file?Plessor
@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.Dowse
@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!Accident
@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.Dowse
L
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_; }
Linwoodlinz answered 6/11, 2012 at 18:20 Comment(4)
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?Larcenous
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.Linwoodlinz
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.Nowt
@jww: In the question, <concrete_x> 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.Linwoodlinz
D
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>.

Disafforest answered 6/11, 2012 at 17:55 Comment(0)
F
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).

Fieldfare answered 6/11, 2012 at 17:57 Comment(2)
This seems okay, only that I'll have a virtual class only to hide the detail of a private member; C++ absurd.Larcenous
@elmes, if your'e thinking about the possibility of different concrete_x's than it's the most natural approach...Tumular

© 2022 - 2024 — McMap. All rights reserved.