1

I have two classes A and B where B uses objects of class A, something like this

class A {
    private:
        int foo_;

    A(const int &foo): foo_(foo) {}
}

class B {

    // STUFF

    inline C operator()(A a)
}

For the moment, I have put the definition of both in a .h file and the code compiles and executes correctly.

My question is: how much can I mask of the implementation of class A, for example by moving code lines to a .cpp file to be separately compiled and linked? Can I mask the implementation of private members and methods (everything which is not directly accessed by an external user)? How? Which C++ keywords should I use?

Thank you very much in advance.

Vitality
  • 20,705
  • 4
  • 108
  • 146

3 Answers3

4

Masking implementations can be done by PIMPL idiom or using simple polymorphism, which is a Factory method pattern. Basically, you create an interface class, say IA like so:

/* File: A.h */
#include <memory> /* For std::shared_ptr */
class IA;
/* Change the line below to boost::shared_ptr<> or
 * another implementation of a shared-pointer.
 * Read more:
 * http://en.wikipedia.org/wiki/Smart_pointer#shared_ptr_and_weak_ptr
 */
typedef std::shared_ptr<IA> APtr;

class IA {
public:
    static APtr Create(const int foo);
    IA(){}
    virtual ~IA(){}
    virtual void somePublicMethod() = 0;
};

In your A.cpp you'll have it's implementation:

/* File: A.cpp */
#include "A.h"

class A : public IA
{
public:
    A(const int foo):foo_(foo){}
    void somePublicMethod(){/* Your awesome implementation goes here */}
};

APtr IA::Create(const int foo)
{
    return APtr(new A(foo));
}

This way, you pass around only the interface and expose only the public methods to outside world, which the internals are in your CPP files.

Advantages:

  • Hide implementation completely from users

Disadvantages:

  • You'll need to create an interface for every class you intend to hide
  • Your users will have to call the factory method to create an instance. For e.g. Create() in the above example.
  • You will always have your class instances to be in heap memory than in stack, i.e., your implementation instances will always have to be a pointer. (Read More: Heap vs. Stack Memory)
Vite Falcon
  • 6,575
  • 3
  • 30
  • 48
  • Except that's not the pImpl idiom you've illustrated, it's just normal polymorphism with an abstract interface (with some pretty severe consequences - you're forcing clients to use a factory and pointers to objects whereas classes using pImpl should be able to present value semantics if they want). See Oli's link for a correct implementation. – Tony Delroy Jun 11 '13 at 08:19
  • Yup! I see your point. I was wrong in that. Changing my answer. – Vite Falcon Jun 11 '13 at 08:22
  • @ViteFalcon Thank you very much for your interesting answer. Three more questions, I hope you could kindly answer. 1) Does what I see now account for Tony D's comment? 2) Should I then create an interface for each class I would like to mask? 3) Should I expose somewhere the prototype of `somePublicMethod()`? Again, thanks. – Vitality Jun 11 '13 at 08:52
  • 1
    @JackOLantern: 1) no it does not. It's still no pimpl, but a factory pattern (an unsafe one, too - it should return a smart pointer). 2) Depends on how strongly you would want to mask it. In most cases you just do the usual division into header and source, as that should be enough. 3) The virtual method declaration *is* the prototype. – Arne Mertz Jun 11 '13 at 09:52
  • @JackOLantern: What ArneMertz said is correct. This relies on factory pattern to create instances of your interface. The implementation I've provided is unsafe in terms of "if you don't remember to do your own memory management". But for the same of completeness, I'll change the code to be 'safe'. – Vite Falcon Jun 11 '13 at 18:23
  • @ViteFalcon Thank you for your effort. Very last question: could you recommend some reference (e.g., book) on PIMPL and the Factory Method Pattern where I could read more, namely, a little bit more expanded as compared to the links you and Oli have provided? Anyway, accepted as the answer. – Vitality Jun 11 '13 at 20:31
  • @JackOLantern: This is one book I've relied on for OOP design patterns: http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612 – Vite Falcon Jun 11 '13 at 21:20
2

Regarding what the pImpl idiom can hide:

  • you can move all function definitions (the implementation thereof, including constructors and their initialisation lists) into the Impl class
  • which then lets you put private types, data members functions into the Impl class EXCEPT where they affect class behaviour (e.g. private constructors, destructors and operators have implications for the user of the class)
  • you can move definitions and initialisation of class static data members into the implementation file
  • as per Arne's comment, private base classes can typically be moved into the implementation (unless you're using typedefs etc. from them in the public or protected data members, and that's ugly as per his comment!)
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • private base classes are implementation details and can be made private base classes of the `Impl` class. That might force you to use the "strongest" form of pimpl idiom, making the `Impl` class what the actual class would have been and `A` itself a mere proxy. – Arne Mertz Jun 11 '13 at 08:23
  • typedefs in the classes you use for implementation of `A` are implementation details as well, that you will want to hide, if you *really* want to create an opaque type. You'll have to define your own return and parameter types and map them to the types used in the `Impl`. That's what many people omit by poisoning their code with WinAPI's `DWORD`, `TCHAR` and whatnot. – Arne Mertz Jun 11 '13 at 09:07
  • @ArneMertz: spot on - I'll update the answer accordingly. Cheers – Tony Delroy Jun 11 '13 at 09:08
  • @TonyD Thanks for your answer. I will take into account your points. – Vitality Jun 11 '13 at 20:29
2

If you don't need C operator()(A a) to be inline you could forward declare the parameter A as follows

class A;

Then you can move its definition to another header and include it in the places it's used.

Here are more details about forward declaration.

Community
  • 1
  • 1
doctorlove
  • 18,872
  • 2
  • 46
  • 62
  • 1
    I was thinking along similar lines but in your answer you haven't tackled the fact that in `inline C operator()(A a)`, `a` is passed by value. – TooTone Jun 11 '13 at 08:25
  • @doctorlove So, from the link about the forward declaration post, your answer will be helpful in the case when the `operator` overload is not inlined and `a` is passed by reference, right? – Vitality Jun 11 '13 at 09:01
  • From the post I linked, that's "or" not "and" i.e. you can "declare functions or methods which accepts/return incomplete types" (and then defined them in your cpp file) OR "Define functions or methods which accepts/return pointers/references to the incomplete type". – doctorlove Jun 11 '13 at 09:09
  • unless you actually dereference the pointer/reference and try to *use* the underlying type - but the compiler will tell you that ;) – Arne Mertz Jun 11 '13 at 09:11
  • @ArneMertz agreed. I hadn't realized that a function of the form `void TakeA(A a);` or `A ReturnA();` would actually compile without defining A. But, in practice, as you point out, if you're trying to hide `A`, then it is helpful to pass `a` as `const A& a`, because then you can have client code that is given a reference to `A` and can pass it to `B` without having to see the definition of `A`. – TooTone Jun 11 '13 at 09:30