How can I use a private member variable in a non-member function, when the variable happens to be a pointer?

StackOverflow https://stackoverflow.com/questions/23320487

  •  10-07-2023
  •  | 
  •  

Question

Essentially my problem is that a function in a library I'm using, (function Foo in this code), requires a pointer to an object (Object* mbar) as a parameter. However, mbar is a private member variable to bar.

Normally, I'd just use a getter and pass by value, but if I pass the pointer, that would give direct access to the resource, which would break encapsulation. Any code could just call the getter and get free reign to modify it.

The next thing I thought was that I could use const pointers because they disallow modifying the resourse they point to, but as far as I could tell, I'd need to modify Foo to accept it, which is impossible as it's a library function.

The final thing I can think of is simply using a friend of Bar to call FoobarFunction, but I've always been told that friend functions are a last resort.

Is there a way to do this without breaking encapsulation in some way?

//Main.cpp

#include "Foobar.h"

int main()
{
    Foobar fb;
    Bar b;
    fb.FoobarFunction(b);
    return 0;
}

//Bar.h

#include "Object.h"

class Bar
{
private:
    Object* mbar;
};

//Foobar.h

#include "Foo.h"
#include "Bar.h"

class Foobar
{
public:
    void FoobarFunction(Bar bar)
    {
        Foo(bar.mbar);
    }
};
Was it helpful?

Solution

The Easy Way Out

You can make the pointer const and then cast it when you pass it to the library function

Foo(const_cast<Object *>(bar.mbar));

This will work if Foo does not try to modify mbar. The cast removes the constness "in name only." Attempting to modify a secretly-const value can lead to Terrible Things.

But Really...

Even if there was a way to make Bar return a "read-only" pointer, the code sample in your question would still violate encapsulation. This particular flavor of non-encapsulation is called feature envy: the data lives in one object, but another object is doing most of the data manipulation. A more object-oriented approach would be to move the manipulation and the data into the same object.

Obviously, the sample code you've given us is much less complicated than your actual project, so I can't know the most sensible way to restructure your code. Here are a couple of suggestions:

  1. Move the FoobarFunction into Bar:

    class Bar
    {
    private:
        Object* mbar;
    public:
        void FoobarFunction()
        {
            Foo(mbar);
        }
    };
    
  2. Use dependency injection. Initialize mbar before creating Bar, then pass mbar into Bar's constructor.

    int main()
    {
        Object *mbar;
        Foobar fb;
        Bar b(mbar);
        fb.FoobarFunction(mbar);
        return 0;
    }
    

    In this example, Bar is no longer the "owner" of mbar. The main method creates mbar directly and then passes it to whoever needs it.

    At first glance, this example appears to break the guideline I mentioned earlier (the data and behavior are stored in different objects). However, there is a big difference between the above and creating a getter on Bar. If Bar has a getMBar() method, then anybody in the world can come along and grab mbar and use it for whatever evil purposes they wish. But in the above example, the owner of mbar (main) has complete control over when to give its data to another object/function.

Most object-oriented languages besides C++ don't have a "friend" construct. Based on my own experience, dependency injection is a better way of solving many of the problems that friends were designed to solve.

OTHER TIPS

If the member is private, it's probably private for a reason...

If Bar has to be the only owner of Obj, then it should not expose it, as any other change to Obj might cause Bar to act incorrectly. Although, if Bar does not have to be the only owner of Obj, you can either put a getter use dependency injection and pass it into Bar from outside, this way you can later pass it to foo as well.

A solution i think you should avoid is putting a call to foo inside Bar. This might violate the Single Responsibility Principle

I bealive that in this case tough, you can use a friend method. I will refer you to a FAQ claiming that friend is not allways bad for encapsulation.

No! If they're used properly, they enhance encapsulation.

You often need to split a class in half when the two halves will have different numbers of instances or different lifetimes. In these cases, the two halves usually need direct access to each other (the two halves used to be in the same class, so you haven't increased the amount of code that needs direct access to a data structure; you've simply reshuffled the code into two classes instead of one). The safest way to implement this is to make the two halves friends of each other.

If you use friends like just described, you'll keep private things private. People who don't understand this often make naive efforts to avoid using friendship in situations like the above, and often they actually destroy encapsulation. They either use public data (grotesque!), or they make the data accessible between the halves via public get() and set() member functions. Having a public get() and set() member function for a private datum is OK only when the private datum "makes sense" from outside the class (from a user's perspective). In many cases, these get()/set() member functions are almost as bad as public data: they hide (only) the name of the private datum, but they don't hide the existence of the private datum.

Similarly, if you use friend functions as a syntactic variant of a class's public access functions, they don't violate encapsulation any more than a member function violates encapsulation. In other words, a class's friends don't violate the encapsulation barrier: along with the class's member functions, they are the encapsulation barrier.

(Many people think of a friend function as something outside the class. Instead, try thinking of a friend function as part of the class's public interface. A friend function in the class declaration doesn't violate encapsulation any more than a public member function violates encapsulation: both have exactly the same authority with respect to accessing the class's non-public parts.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top