0

I am using the PIMPL approach and would like to access a private member of the implementation class from the regular class. Is there any legitimate way to do this? In the below code, there are two private members I would like to access. One of these is an attribute, the other is a method.

Here is my code:

Dave.h

#include "DaveImpl.h"

class Dave {
    public:
        Dave ();
        int getAge ();
    private:
        int getIq ();
        DaveImpl* _impl;
};

Dave.cc

 #include "Dave.h"

Dave::Dave () {
    _impl = new DaveImpl ();
}

int Dave::getAge () {
    return _impl->age;
}

int Dave::getIq () {
    return _impl->getIq ();
}

DaveImpl.h

class DaveImpl {
    public:
        DaveImpl ();
    private:
       int age;
       int iq;
       int getIq ();
};

DaveImpl.cc

#include "DaveImpl.h"

DaveImpl::DaveImpl () {
    age=60;
    iq=75;
}

int DaveImpl::getIq () {
    return iq;
}

When I compile the above code, I get the following messages:

Dave.cc:8:19: error: ‘int DaveImpl::age’ is private within this context
INFO: 1>     8 |     return _impl->age;
Dave.cc:12:26: error: ‘int DaveImpl::getIq()’ is private within this context
INFO: 1>    12 |     return _impl->getIq ();

Is there any way for me to access the "age" attribute or the "getIq" method in the above scenario ?

Note that I am stuck with the member and method in the implementation class being private. I cannot change that unfortunately.

Laurel
  • 5,965
  • 14
  • 31
  • 57
didjek
  • 393
  • 5
  • 16
  • Make it `protected` for subclasses, and `public` for others. If it's `private` you can't access it outside the class. – anastaciu Sep 15 '20 at 14:26
  • Unfortunately I am stuck with the members in the implementation class being private. – didjek Sep 15 '20 at 14:29
  • You can make a public getter, other than that, it's what it is. – anastaciu Sep 15 '20 at 14:30
  • @didjek why is it a requirement that the `variables` in the `DaveImpl` are `private`? If it is then you need to write getter and setters. – t.niese Sep 15 '20 at 14:31
  • The implementation class is somebody else's code and it is an API I am not permitted to break. So I cannot change the private variables to public. – didjek Sep 15 '20 at 14:33
  • @didjek as a note: if you implement pimpl, you should use `std::unique_ptr _impl` instead of `DaveImpl* _impl;`, this prevents you from doing common mistakes when doing coping and assigning. – t.niese Sep 15 '20 at 14:34
  • 2
    @didjek `The implementation class is somebody else's code and it is an API I am not permitted to break` well then you need to use the functionality the API provides to access these members. If it does not provide an API for that then you should not use those members directly. – t.niese Sep 15 '20 at 14:35
  • 3
    @didjek, if you could access private members at will, what would be the point of `private` modifier? – anastaciu Sep 15 '20 at 14:36
  • I think it's weird that pimpl wrapper and it's implementation are written by different people. – apple apple Sep 15 '20 at 14:47
  • @appleapple that is not necessarily weird. If you can link against that foreign code statically and you don't want others using your library to require to have access to that foreign code. And writing a Impl class that wraps around that is then just some code duplication. – t.niese Sep 15 '20 at 14:55
  • If by weird, you mean its a bad idea, you might well be right (particularly when my IQ is only 75, as this example code indicates)! Time will tell... – didjek Sep 15 '20 at 14:55
  • @t.niese it's not PIMPL then. – apple apple Sep 15 '20 at 14:57
  • well tbh I think @ OP 's code doesn't looks like pimpl either. since it's implementation has header file *which is included in `Dave.h`* – apple apple Sep 15 '20 at 15:02
  • @appleapple why? In PIMPL you have a pointer to the implementation, and the implementation class is forward declared. The class holding the pointer to the implementation provides an API to interact with the implementation, but who says that you need to implement an own class for the implementation? The important part of that idiom is the forward declaration (which breaks compile-time dependency of implementation and the interface provided to the user), so no include the implementation. – t.niese Sep 15 '20 at 15:03
  • @appleapple `OP 's code doesn't looks like pimpl eithe` well that on the other hand is true. `#include "DaveImpl.h"` in `#include "Dave.h"` is exactly what you won't have in PIMPL. – t.niese Sep 15 '20 at 15:04
  • @t.niese I think it's just different understanding of `PIMPL` between us, for me, I would only call it `PIMPL` if it's actually hide it's **own** implementaion. (the implementation is highly coupled with the wrapper). – apple apple Sep 15 '20 at 15:26

2 Answers2

0

A PIMPL is meant to be an hidden implementation, specifically to hide implementation detail to not break public interface. If you state you cannot change DaveImpl because it would break the API, than simply is not an implementation detail, so should not be a PIMPL at all, so you should think about a possible design flaw.

Moreover, if there's a class called DaveImpl which have the implementation detail of what class Dave should expose, why DaveImpl should hide anything to Dave since is written specifically for Dave and not for anyone else? So to get to the point there's no reason to make private member at all. You can change DaveImpl to a struct, making getters/setters for everything or make a friend class. The most logical one is the first though.

How an actual PIMPL should be implemented

Dave.h

class Dave {
    public:
        Dave ();
        int getAge () const;
    private:
        // no reason at all to have this method. It's private so it's 
        // an implementation, then it could be directly called through
        // the pimpl.
        // int getIq () const;

        struct DaveImpl;
        std::unique_ptr<DaveImpl> _impl;
};

Dave.cc

 #include "Dave.h"

struct Dave::DaveImpl {
    DaveImpl() { ... }

    int age;
    int iq;
 
    //int getIq (); no reason at all to have a getter
};

Dave::Dave () 
 : _impl(std::make_unique<DaveImpl>()){
}

int Dave::getAge () const {
    return _impl->age;
}

Wherever you need to call iq in your implementation, just use _impl->iq directly

apple apple
  • 10,292
  • 2
  • 16
  • 36
Moia
  • 2,216
  • 1
  • 12
  • 34
-1

You can declare getter in your impl class in public scope:

class DaveImpl {
public:
    DaveImpl ();
    int getAge() const {return age;} 
    int getIq() const {return iq;}
private:
   int age;
   int iq;
};

and use them:

int Dave::getAge () {
    return _impl->getAge();
}

int Dave::getIq () {
    return _impl->getIq ();
}

Also you can declare Dave class as a friend for your impl class, and Dave class will have an access to private fields of impl:

class DaveImpl {
    friend class Dave;
    public:
        DaveImpl ();
    private:
       int age;
       int iq;
       int getIq ();
};
Enrico
  • 120
  • 5
  • 2
    `friend class DaveImpl;` allows `DaveImpl` do access members of `Dave` not the other way round. – t.niese Sep 15 '20 at 14:39
  • 2
    Besides that, the OP claims that `DaveImpl` is foreign code and no changes to it can be made. – t.niese Sep 15 '20 at 14:40
  • Note that `const` is doing nothing in this context, you are returning a copy of the members, it only makes sense if you are returning a pointer or a reference. – anastaciu Sep 15 '20 at 14:44
  • 1
    Not exactly, I can add code to DaveImpl, but I cannot change existing code (e.g change a member from private to public, or change the signature of an existing method etc). – didjek Sep 15 '20 at 14:44
  • Enrico, how would you change your code above if I did not want to access iq by adding a public getIq () method, but instead wanted to access iq by calling the private getIq() method via a new public getIqImpl () method ? – didjek Sep 15 '20 at 14:51
  • If you want to call the private method, you have two options: either declare it as public (and I think that is not your case) or by declaring class, which is calling this method, as a friend for your impl class. There is no other way to achieve this. – Enrico Sep 15 '20 at 14:56
  • Well, I suppose I could add some new method in DaveImpl, as follows: int getIqImpl () { return getIq (); } – didjek Sep 15 '20 at 14:57
  • 1
    @didjek how can you do that if you claim that you can't change the foreign code? – t.niese Sep 15 '20 at 15:05
  • I can add new methods, but I cannot change existing ones, because that would break the API that other external code is using. – didjek Sep 15 '20 at 15:15
  • @didjek change to public would not break API. – apple apple Sep 15 '20 at 15:33
  • 1
    @didjek but why would make the member public would be a breaking change and adding public member functions would not be? That does not make to much sense. – t.niese Sep 15 '20 at 15:45
  • @t.niese You might be right, in fact, now that I consider the matter. I will take that up with the powers that be. – didjek Sep 15 '20 at 15:49