Thread: Advantages and situations where private data hiding is (not) useful

  1. #1
    Registered User
    Join Date
    Mar 2008
    Location
    Coimbra, Portugal
    Posts
    85

    Advantages and situations where private data hiding is (not) useful

    Recently, I discovered something I should have discovered a long time ago - how to hide a class' private members.

    All of you should know this (you are experts ):

    Code:
    class Implementation; //Forward declaration of the implementation
    
    class Interface {
    public:
    //yodda yodda, public, private, protected methods
    
    Implementation* ptr;
    };
    Then I implement the methods in the Implementation class. All makes sence. Pointer sizes depend on the architecture and do not depend on what they point to, that's obvious (how else would void* exist?)

    I often want to hide the implementation as far as possible, but I understand that this implementation moves the members into the Heap -- that is obviously a slowdown.

    On the other hand, it prevents the user (of the class) from creating additional methods that give him access to the members. But they can always do the same with the pointer can't they? For instance, they can make it point to NULL!

    So, when is it useful to do this and when isn't it? Logically, on proprietary/closed-source libraries this is probably used, or are there other methods?

    Thanks, Jorl17

  2. #2
    Registered User
    Join Date
    Sep 2004
    Location
    California
    Posts
    3,268
    A better (and far more common) way to hide the implementation is to do something like:

    Code:
    // Defines the interface (How the object can be used)
    class Interface
    {
    public:
        virtual ~Interface() {}
        virtual void foo() = 0;
    };
    
    // Object implementation.  This can be hidden from library users.
    class Implementation : public Interface
    {
    public:
        void foo() { ... }
    };
    
    // Mechanism for creating the object.  All they get is an interface pointer though, so they don't
    // know how the interface was implemented.
    class SomeFactory
    {
    public:
        static Interface* CreateObject() { return new Implementation(); }
    };

  3. #3
    Registered User
    Join Date
    Mar 2008
    Location
    Coimbra, Portugal
    Posts
    85
    Thanks for that.

    That's just like COM, or am I wrong?

  4. #4
    Registered User
    Join Date
    Sep 2004
    Location
    California
    Posts
    3,268
    It is similar to COM. COM adds the concept of being able to query for an interface, and reference counting all the underlying objects as well.

  5. #5
    Registered User
    Join Date
    Mar 2008
    Location
    Coimbra, Portugal
    Posts
    85
    When you say that implementation is better, why do yu believe it is?

    It must have its advantages and disadvantanges. For instance, it seems to me that the first implementation is coded in such a way that a programmer only needs to declare an instance of the class. Whereas this implementation doesn't seem to function that way.

  6. #6
    Registered User
    Join Date
    Sep 2004
    Location
    California
    Posts
    3,268
    The problem with your example is that it doesn't actually hide anything from the user. If the user has the header file (Implementation.h), then they can bypass the use of the interface class entirely. It also limits the Interface author's ability to swap out the interface implementation with a different class because it will require a change to the Interface class to accomplish this. That can break backwards compatibility with code that already uses the library.

    One of the most valuable things that interfaces provide is the ability to be flexible. You lose this advantage as soon as you include a pointer to the implementing class in the interface.

  7. #7
    The larch
    Join Date
    May 2006
    Posts
    3,573
    On the other hand, it prevents the user (of the class) from creating additional methods that give him access to the members.
    I thought the main advantage is shorter compile times. PImpl gives you more freedom to tinker around with the internal implementation details - adding/removing/changing data members, without requiring recompiling other files depending on the header (can be useful if the class is complicated enough so there may be a need to change data members and such, and included in many other headers).

    But then I program alone on my projects and don't sabotage my own classes

    For instance, it seems to me that the first implementation is coded in such a way that a programmer only needs to declare an instance of the class. Whereas this implementation doesn't seem to function that way.
    They probably have somewhat different usages, I guess (one requires polymorphism, the other not).
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  8. #8
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by bithub View Post
    One of the most valuable things that interfaces provide is the ability to be flexible. You lose this advantage as soon as you include a pointer to the implementing class in the interface.
    No you don't. You just forward declare the implementation class.

    Code:
    class Implementation;
    
    class Interface
    {
    private:
        Implementation *pImpl;
    };
    Then, in the .cpp which actually implements the Interface, you define (and implement) the Implementation. This is an extremely common pattern...
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  9. #9
    Registered User
    Join Date
    Mar 2008
    Location
    Coimbra, Portugal
    Posts
    85
    Thank you both.

    bithub, I understand what you say, except for the part where you say that one can use the implementation directly. No one will ever have implementation.h, simply because that is really hidden and previously compiled either as a library or as an object file. (AFAIK)

    I don't, however, understand what changes one must do to the interface when using this technique. If we keep the implementation class backwards compatible, then the interface class will too, I think. What happens is that if we implement a new function, we have to do it everywhere, in the implementation and in the interface.

    Interface.h
    Code:
    class Implementation; //Forward class declaration
    class Interface {
    Interface(int);
    void setValue(int);
    int getValue(void) const;
    ~Interface();
    
    private:
    
    Implementation* ptr;
    };
    Implementation.h
    Code:
    class Implementation {
    
    int v;
    
    public:
    
    Implementation (int val) : v(val) { }
    
    void setValue ( int val ) { v = val }
    
    void getValue(void) const { return v; }
    };
    interface.cpp - Might be hidden from the programmer under a library or an object
    Code:
    Interface:.Interface ( const int val ) : ptr ( new Implementation(val) ) { }
    void Interface::setValue(int val) { ptr->setValue( val ); }
    int Interface::getValue() const { return ptr->getValue(); }
    Interface::~Interface(void) { delete ptr; }
    @anon: I had never thought about that advantage, and it sure makes sense. But a small part of what we gain in compile time, we lose in run-time speed, right?

  10. #10
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by Jorl17 View Post
    I don't, however, understand what changes one must do to the interface when using this technique. If we keep the implementation class backwards compatible, then the interface class will too, I think. What happens is that if we implement a new function, we have to do it everywhere, in the implementation and in the interface.
    Not necessarily. The Implementation class does not have to mirror the Interface class. In fact, it could look completely different. That's the whole point of separating the interface from the implementation.

    In other words, the Interface does NOT simply forward calls into the Implementation. That completely defeats the point. When the Implementation changes, the code inside the Interface might change, but this is completely invisible to the user of the Interface.

    The pimpl idiom does NOT protect you from breaking the interface when you, well, change the interface. Interfaces should be stable. Implementations can vary. If you find yourself needing to change the Interface, then you designed it incorrectly in the first place, and you WILL force a recompile and possibly code changes for the users of that Interface.

    In short, don't change interfaces. If you find that an interface is lacking in some way, you should define a NEW interface with a NEW name. This way, users of the old interface can continue to use it, and users who need the new features can use the new interface. Both interfaces might share the same underlying Implementation. Or not. That's the flexibility this pattern grants you.
    Last edited by brewbuck; 06-17-2009 at 04:20 PM.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  11. #11
    Registered User
    Join Date
    Mar 2008
    Location
    Coimbra, Portugal
    Posts
    85
    Yes, my bad, that is correct. the interface may not mirror the implementation and simply use its dark-evil-and-hidden functions to do whatever the dark-force wants it to .

    In other words, the Interface does NOT simply forward calls into the Implementation. That completely defeats the point. When the Implementation changes, the code inside the Interface might change, but this is completely invisible to the user of the Interface.
    Exactly! Thanks!

    In short, don't change interfaces. If you find that an interface is lacking in some way, you should define a NEW interface with a NEW name. This way, users of the old interface can continue to use it, and users who need the new features can use the new interface. Both interfaces might share the same underlying Implementation. Or not. That's the flexibility this pattern grants you.
    While I had never thought about it, that's what i've done up until now. For instance, I developed a simple network layer over TCP which used one way of doing things. Then, I developed another version that did the exact same thing and had the exact same procedures, but a completely different (and better) implementation. It was as simple as copy-paste to get it into my apps.
    Last edited by Jorl17; 06-17-2009 at 04:23 PM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Binary Search Trees Part III
    By Prelude in forum A Brief History of Cprogramming.com
    Replies: 16
    Last Post: 10-02-2004, 03:00 PM