1

Encapsulation (information hiding) is a very useful concept, ensuring that only the barest minimal details are published in the API of a class.

But I can't help thinking that the way C++ does this is a little deficient. Take, for example, a (Celsius-based) temperature class like:

class tTemp {
    private:
        double temp;
        double tempF (double);
    public:
        tTemp ();
        ~tTemp ();
        setTemp (double);
        double getTemp ();
        double getTempF ();
};

Now, that's a very simple case but it illustrates a point that the encapsulation isn't perfect. "Real" encapsulation would hide all unnecessary information such as:

  • the fact that the data is maintained internally in the temp variable (and its type).
  • the fact that there is an internal routine for Fahrenheit/Celsius conversion.

So, ideally, it seems to me that the implementor of the class would use the above header but any client of the class would see just the public bits.

Don't get me wrong, I'm not criticising C++ since it meets the stated purpose of preventing clients from using the private bits but, for more complex classes, you could easily work out internal details based on the names, types and signatures of private data and functions.

How does C++ allow implementors to hide this information (assuming it is possible)? In C, I'd simply use an opaque type so that the internal details would be hidden but how would you do that in C++?

I suppose I could maintain an separate class, totally hidden from the client and known only to my own code, and then keep an instance of it with a void * in the visible class (casting within my code), but that seems a rather painful process. Is there an easier way in C++ to achieve the same end?

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Can't wait until modules get sorted out :) – chris Jul 25 '13 at 02:03
  • 1
    Someone has already mentioned Pimpl, which you more or less indirectly mention, but with more safety than void *. Another approach is to declare an interface class (signatures are pure virtual), and a factory, which is the only one privy to the inner workings of concrete derived classes. – Don Wakefield Jul 25 '13 at 02:11
  • What you are calling "true" encapsulation is *possible* in C++ - as the many *pimpl* answers attest - but it comes at a cost. Hiding the internals from *users* is all well and good, but hiding them from the *compiler* inhibits optimization. It's a fundamental design goal of C++ that performance wins out whenever there is a conflict between performance and some other feature. – Casey Jul 25 '13 at 04:54
  • 1
    @Casey, that's an interesting comment but I can find no support for it in the ISO standards :-) Some of us actually value readability above performance by default and only optimise when it's found to be _necessary._ In any case, I'm not convinced PIMPL hides a great deal from the compiler since the code (both visible and private) is all localised in one area that the compiler can see. – paxdiablo Jul 25 '13 at 05:12

4 Answers4

8

C++ uses an idiom known as "pimpl" (private implementation / pointer to implementation) to hide implementation details. Take a look at this MSDN article for details.

In short, you expose your interface in a header file as normal. Let's use your code as an example:

tTemp.h

class tTemp {
    private:
        class ttemp_impl; // forward declare the implementation class
        std::unique_ptr<ttemp_impl> pimpl;
    public:
        tTemp ();
       ~tTemp ();
       setTemp (double);
       double getTemp (void);
       double getTempF (void);
};

The public interface remains, but the private internals have been replaced with a smart pointer to a private implementation class. This implementation class is located only in the header's corresponding .cpp file, it is not exposed publicly.

tTemp.cpp

class tTemp::ttemp_impl
{
    // put your implementation details here
}

// use the pimpl as necessary from the public interface
// be sure to initialize the pimpl!
tTtemp::tTemp() : pimpl(new ttemp_impl) {}

This also has the added advantage of allowing you to change the internals of your class without changing the header, which means less recompiling for users of your class.


For a full solution as shown in paxdiablo's pre-C++11 answer, but with unique_ptr instead of void *, you can use the following. First ttemp.h:

#include <memory>
class tTemp {
public:
    tTemp();
    ~tTemp();
    void setTemp(double);
    double getTemp (void);
    double getTempF (void);

private:
    class impl;
    std::unique_ptr<impl> pimpl;
};

Next, the "hidden" implementation in ttemp.cpp:

#include "ttemp.h"

struct tTemp::impl {
    double temp;
    impl() { temp = 0; };
    double tempF (void) { return temp * 9 / 5 + 32; };
};

tTemp::tTemp() : pimpl (new tTemp::impl()) {};

tTemp::~tTemp() {}

void tTemp::setTemp (double t) { pimpl->temp = t; }

double tTemp::getTemp (void) { return pimpl->temp; }

double tTemp::getTempF (void) { return pimpl->tempF(); }

And, finally, ttemp_test.cpp:

#include <iostream>
#include <cstdlib>
#include "ttemp.h"

int main (void) {
    tTemp t;
    std::cout << t.getTemp() << "C is " << t.getTempF() << "F\n";
    return 0;
}

And, like paxdiablo's solution, the output is:

0C is 32F

with the added advantage of more type safety. This answer is the ideal solution for C++11, see paxdiablo's answer if your compiler is pre-C++11.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
drwwlkr
  • 180
  • 5
  • So you don't need the `ttemp_impl` _defined_ in the visible code since you're storing a pointer to the forward declared (opaque) class. Nice. I gather in the implementor code I would fully define `ttemp_impl` before `tTemp` (hence ignoring the fwd-declaration) so that I would have access to the internals. And the data/functions would have to be public in `ttemp_impl` so I could call them, yes? Such as `pimpl->temp`, `far = pimpl->cToF(temp);` and so on. – paxdiablo Jul 25 '13 at 02:27
  • @paxdiablo Right. The impl class will need to be defined before being used, and anything the public class will need from it should be public. In practice, most of the public functions will end up being thin wrappers that forward to impl functions. – drwwlkr Jul 25 '13 at 03:06
  • I can't for the life of me get this to compile without the dreaded "ISO C++ forbids declaration of 'unique_ptr' with no type" error, even with the forward declaration. Not sure if this is a C++11 feature you're using here but I've had to resort to `void*` to get it working. – paxdiablo Jul 25 '13 at 03:55
  • I've simplified the question and your answer to get rid of unnecessary detail (unused functions mostly). I couldn't get the unique_ptr variant working without that error but I managed to get a void* variant going (see my answer). If you can get the unique_ptr one to compile based on my test code, I'll accept the answer. The reason I asked abt C++11 is that I'm using gcc 4.3.4 (CygWin) and it may lack support for some things, even _with_ `-std=c++0x`. – paxdiablo Jul 25 '13 at 04:24
  • `std::unique_ptr` was added in C++11. For C++ <11, Use a raw pointer: `class ttemp_impl* pimpl;`. Beware that the compiler generated copy constructor will almost certainly do the wrong thing; you will have to write your own copy constructor. – Casey Jul 25 '13 at 04:48
  • @Casey, the g++ compiler is not complaining about unique_ptr itself, just about using it with no type. I've used `-std=c++0x` in g++ but cannot get it to lose that error shown. It may be that my g++ is old enough that proper support for this feature is missing but it recognises unique_ptr well enough. – paxdiablo Jul 25 '13 at 05:05
  • @paxdiablo You shouldn't be declaring it with "no type", you should be declaring it with the incomplete type of the implementation class as in the code in the answer: `class ttemp_impl; std::unique_ptr pimpl;` If it won't work with an incomplete type, then the implementation of `unique_ptr` is deficient. – Casey Jul 25 '13 at 05:08
  • @paxdiablo It's also important that the implementation class is complete when the compiler tries to instantiate the destructor for the `unique_ptr`: you will therefore need to define the constructors and destructor for the *interface* class where the definition of the implementation class is visible. (I'm trying to guess what your compile problem is - you should probably post another question if you still have issues). – Casey Jul 25 '13 at 05:13
  • @Casey, yes, I suspect very much that the implementation is deficient. Neither the code at MSDN nor here compiled in CygWin without the error - what I was after in my comments above was a known-to-work-under-C11 variation of my own answer so I could definitely confirm (1) the answer is okay; (2) CygWin gcc isn't up to scratch. I'll follow your advice and post another question on the problematic situation. – paxdiablo Jul 25 '13 at 05:14
  • Turns out it was very much the old gcc version. I changed my code to a form specified by this answer and tried it under Debian gcc 4.7.2 and it worked perfectly. So I've added some extra meat at the end and accepted this answer. Thanks, diw and @Casey. – paxdiablo Jul 25 '13 at 11:21
4

Thought I would flesh out the "interface class / factory" technique that Don Wakefield mentions in his comment. To start with, we abstract away all implementation detail from the interface and define an abstract class that contains only the interface to a Temp:

// in interface.h:
class Temp {
    public:
        virtual ~Temp() {}
        virtual void setTemp(double) = 0;
        virtual double getTemp() const = 0;
        virtual double getTempF() const = 0;

        static std::unique_ptr<Temp> factory();
};

Clients that want a Temp object call the factory to build one. The factory could provide some complicated infrastructure that returns different implementations of the interface in different conditions, or something as simple as the "just give me a Temp" factory in this example.

It's possible for implementation classes to implement the interface by providing overrides for all of the pure virtual function declarations:

// in implementation.cpp:
class ConcreteTemp : public Temp {
    private:
        double temp;
        static double tempF(double t) { return t * (9.0 / 5) + 32; }
    public:
        ConcreteTemp() : temp() {}
        void setTemp(double t) { temp = t; }
        double getTemp() const { return temp; }
        double getTempF() const { return tempF(temp); }
};

and somewhere (possibly in the same implementation.cpp) we need to define the factory:

std::unique_ptr<Temp> Temp::factory() {
    return std::unique_ptr<Temp>(new ConcreteTemp);
}

This approach is a little more easily extensible than pimpl: anyone who wants to can implement the Temp interface instead of there being only one "secret" implementation. There's also a bit less boilerplate since it's using the language's builtin mechanisms for virtual dispatch to dispatch interface function calls to implementations.

Casey
  • 41,449
  • 7
  • 95
  • 125
  • 2
    The pro of this method is that there is no extra allocation (nice when the class is intended to be constructed on the stack). The con is that every method has to be virtual which is more expensive to invoke. These details only matter if you have specific performance issue on your application. – Benlitz Jul 25 '13 at 06:50
1

There is a non-orthodox approach I've seen used by pugi::xml_document from the pugixml library, and it doesn't have the overheads of pimpl or abstract classes. It goes like this:

You reserve a char array in your publicly exposed class:

class tTemp {
public:
    tTemp();
    ~tTemp();
    void setTemp(double);
    double getTemp();
    double getTempF();

    alignas(8) char _[8]; // reserved for private use.
};

Note that

  • the alignment and size in this example are hardcoded. For a real application you would use an expression to estimate that based on the size of the machine word, for example sizeof(void*)*8 or similar.
  • adding private won't provide any additional protection because any access to _ can just as well be replaced with a cast to char*. It's the lack of implementation details in the header that provides the encapsulation.

Next, in the translation unit, you can implement tTemp as follows:

struct tTempImpl {
    double temp;
};
static_assert(sizeof(tTempImpl) <= sizeof(tTemp::_), "reserved memory is too small");

static double tempF(tTemp &that) {
    tTempImpl *p = (tTempImpl*)&that._[0];
    return p->temp * 9 / 5 + 32;
}

tTemp::tTemp() {
    tTempImpl *p = new(_) tTempImpl();
}

tTemp::~tTemp() {
    ((tTempImpl*)_)->~tTempImpl();
}

tTemp::tTemp(const tTemp& orig) {
    new(_) tTempImpl(*(const tTempImpl*)orig._);
}

void tTemp::setTemp(double t) {
    tTempImpl *p = (tTempImpl*)_;
    p->temp = t;
}

double tTemp::getTemp() {
    tTempImpl *p = (tTempImpl*)_;
    return p->temp;
}

double tTemp::getTempF() {
    return tempF(*this);
}

This is, surely, more verbose compared to other presented approaches. But this is the only zero-overhead approach I know that can truly hide all compile-time dependencies from the headers. Note that it also provides a degree of ABI stability -- you can change tTempImpl as long as its size does not exceed the reserved memory.

For a more detailed discussion about encapsulation in C++ see my True encapsulation in C++ blog post.

Yakov Galka
  • 70,775
  • 16
  • 139
  • 220
0

Private implementation (PIMPL) is the way in which C++ can provide this feature. Since I had trouble getting the unique_ptr variation to compile with CygWin g++ 4.3.4, another way to do it is to use a void * within your visible class as follows. This will allow you to use pre-C++11 compilers, and compilers like the aforementioned gcc which only had experimental support for C++11.

First, the header file ttemp.h, the one the client includes. This declares opaquely the internal implementation structure so that those internals are fully hidden. You can see that the only detail revealed is the name of the internal class and variable, neither of which need to reveal any information on how the internals work:

struct tTempImpl;
class tTemp {
public:
    tTemp();
    ~tTemp();
    tTemp (const tTemp&);
    void setTemp(double);
    double getTemp (void);
    double getTempF (void);
private:
    tTempImpl *pimpl;
};

Next, the implementation file ttemp.cpp which both declares and defines the opaque stuff, and also defines the user-visible details. Since the user never sees this code, they do not know about how it's implemented:

#include "ttemp.h"

struct tTempImpl {
    double temp;
    tTempImpl() { temp = 0; };
    double tempF (void) { return temp * 9 / 5 + 32; };
};

tTemp::tTemp() : pimpl (new tTempImpl()) {
};

tTemp::~tTemp() {
    delete pimpl;
}

tTemp::tTemp (const tTemp& orig) {
    pimpl = new tTempImpl;
    pimpl->temp = orig.pimpl->temp;
}

void tTemp::setTemp (double t) {
    pimpl->temp = t;
}

double tTemp::getTemp (void) {
    return pimpl->temp;
}

double tTemp::getTempF (void) {
    return pimpl->tempF();
}

Note that the internal implementation details are not protected in any way from the visible class itself. You could define the internals as a class with accessors and mutators but it seems unnecessary since it should be tightly coupled in this case.

One word of note from above: because you're using a pointer to control the hidden aspects, the default shallow copy constructor would cause grief by having two visible objects referring to the same private member (leading to a double-delete in the destructor). So you need to (as I have) provide a deep-copy copy constructor to prevent this.

Lastly, a test program showing how the whole thing hangs together:

#include <iostream>
#include "ttemp.h"

int main (void) {
    tTemp t;
    std::cout << t.getTemp() << "C is " << t.getTempF() << "F\n";
    return 0;
}

The output of that code being, of course:

0C is 32F
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953