2

When using encapsulation and "tell, don't ask"-principle properly, there should be no reason for one to ask information from an object. However, I've ran into a situation (let me know if this design itself is terrible) where I have an object with a member variable pointing to a function outside of the class.

At some point of my application, there's a need for my object to call the function and the function should then act based on my object's status.

Here's an example class:

typedef void(*fptr)(Foo*);
class Foo {
    public:
        Foo(string name, fptr function);
        void activate()
        {
            m_function(this);
        }
    private:
        string m_name;
        fptr m_function;
};

That's the class, now the developer can use the class like so;

void print(Foo *sender)
{
    cout << "Print works!" << endl;
}

int main(int argc, char **argv)
{
    Foo foo("My foo", &print);
    foo.activate();
    // output: "Print works!"
}

This all works fine, but what if I want to print the name of the sender? All the functions are defined outside of the class, by other developers, so there's no way to access private variables. In C#, you can just use the partial keyword to add a method to an existing class. This is not possible in C++ though.

I could just ignore encapsulation and create a setter and getter for name and all other properties that might be needed by the function in the future. This is pretty terrible solution, I should basically create setter and getter for everything there is in my class, since the function can do anything to my object. Besides what's the reason of encapsulation, if I'm just gonna ignore it when I want to?

An other solution would be a struct that holds the required properties inside it:

struct FooBar {
    string name;
};

typedef void(*fptr)(FooBar);

void Foo::activate()
{
    FooBar fb;
    fb.name = m_name;
    m_function(fb);
}

But this is not much different from not using encapsulation, and it doesn't seem like a too good solution either. What would be the best approach for this problem?

Daniel Daranas
  • 22,454
  • 9
  • 63
  • 116
Skamah One
  • 2,456
  • 6
  • 21
  • 31
  • See [this old question](http://stackoverflow.com/questions/1568091/why-use-getters-and-setters). – Some programmer dude Jun 05 '13 at 12:17
  • 1
    @JoachimPilborg I think he's trying to *avoid* getters and setters, not trying to find a reason to use them. –  Jun 05 '13 at 12:27
  • 1
    If you only need to observe the data, then having getters and no setters seems like the best solution. Either you want the data to be public or private. Being able to access the data in a function created by anybody makes the data effectively public. – Juraj Blaho Jun 05 '13 at 12:47
  • @JurajBlaho Callbacks are called under very specific circumstances, so making the members only available to callbacks can be a useful form of access control. If you're trying to abuse the callbacks to "make the data effectively public", you're digging your own grave. As Herb Sutter says, we need to protect against Murphey, not Machiavelli. – Rick Yorgason Jun 05 '13 at 13:27
  • @RickYorgason: Unfortunately there is Murphy even in implementing the callbacks. The code of callbacks is as good as any other user code. I don't see much difference. And as I proposed having public getters may introduce a bit of coupling, but it cannot invalidate class' invariants. So there is no Machiavelli or Murphy in there. Actually that seems to be the best way to do it. – Juraj Blaho Jun 05 '13 at 21:58

6 Answers6

1

Seen from the outside, private variables don't exist, so developers cannot possibly "want" to print them.

If they do want then either the class members (or better, queries in the class returning their contents) should be public, the function a member of the class, or in specific cases some friend mechanism may be used.

To summarize, don't set out to break encapsulation - instead, reconsider the abstraction behind your encapsulation and, if needed, create new queries for properties of your class which weren't foreseen as useful back when the class was designed - but now are.

Daniel Daranas
  • 22,454
  • 9
  • 63
  • 116
1

I would make activate() an abstract method and all the class' properties protected. Also, there's no need for the fptr:

class Foo {
public:
    Foo(string name);
    virtual void activate() = 0;
protected:
    string m_name;
};

Now when someone wants to use your class, he just inherits his own from it:

class MyFoo : public Foo {
public:
    MyFoo(string name);
    virtual void activate()
    {
        cout << m_name << " says: Hello World!" << endl;
    }
};

int main(int argc, char **argv)
{
    MyFoo foo("My foo");
    foo.activate();
    // output: "My Foo says: Hello World!"
}

And if you need many different Foo's with different functionality, just inherit multiple classes instead of declaring multiple functions.


Edit: Instead of inheriting a new class for every different Foo instance, you could inherit one class for all of them with all the different methods. Now all left for activate is to decide which method to call; use enum for this:

enum MyFooFunction {
    printName,
    printHello
};

class MyFoo : public Foo {
public:
    MyFoo(string name, MyFooFunction function);
    void printName() { cout << m_name << endl; }
    void printHello() { cout << "Hello!" << endl; }
    virtual void activate()
    {
        switch(m_function) {
        case printName:
            printName();
            break;
        case printHello:
            printHello();
            break;
        }
    }
protected:
    MyFooFunction m_function;
};
  • I might have up to 20 different types of `Foo`, it seems more reasonable to define 20 functions rather than inherit 20 classes... – Skamah One Jun 05 '13 at 12:30
  • This design in the edit is completely bad, since the outside world should be able to add new functionalities without touching the inside of the class. The completely contrary to the open/closed principle. – Ralph Tandetzky Jun 05 '13 at 12:39
  • @RalphTandetzky Why exactly should the outside world be able to add new functionalities without touching the class? Just curious. –  Jun 05 '13 at 12:41
  • That's the whole idea of interfaces and plugins. They should be customizable. You don't want to touch someone else code all the time in order to inject some new functionality. – Ralph Tandetzky Jun 05 '13 at 12:43
  • The `enum` and `MyFoo` are both already defined in the "outside world". If the outside of the outside world needs to add even more functionality (I can't think of such case), he can just inherit a new class from `MyFoo`, right? –  Jun 05 '13 at 13:00
  • Your original suggestion is reasonable. It's a bit verbose, and it doesn't work when you want to use multiple callbacks on the same object, but it's a good choice in some circumstances. Your edit, however, doesn't make much sense to me. It's more verbose and less efficient than just using multiple classes. – Rick Yorgason Jun 05 '13 at 13:01
1

Let's face it, C++ access control was designed with some use cases in mind, and are generally usable, but never claimed to cover everything. If you can't solve the situation with just private and friend, and arbitrary functions must be allowed to access the internals, then best way is to make them public and move on.

Setters will not move you forward for sure, just add complexity for nothing. If data is effective public don't try to mask that fact pretending like it wasn't.

Look for the root cause -- why on earth outsides want your members and rearrange that.

Balog Pal
  • 16,195
  • 2
  • 23
  • 37
1

You might want to change your function parameter type to const string & if the function should be able to see the string, but the rest of the outside world shall not see it. Also you might consider to use std::function<void(const string &)> instead of your function type. This has two fundamental advantages: You can pass closures (also called lambdas) to your constructor and you can read it more easily. The edited code would look like this:

class Foo {
    public:
        template <typename F>
        Foo(string name, F && function) 
            : m_name    (std::move(name))
            , m_function(std::forward<F>(function))
        {
        }

        void activate()
        {
            m_function(m_name);
        }
    private:
        string m_name;
        std::function<void(const string &)> m_function;
};

The client code would look like

int main(int argc, char **argv)
{
    Foo foo("My foo", [](const string & s){ cout << s << endl; });
    foo.activate();
    // output: "My foo"
}

You see that the client does not need to define an extra function, but can simply do it 'inline'.

Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120
1

What you're asking is "How can I keep my members private, but still give callbacks some way of accessing them?"

When you look at it that way, your struct FooBar solution is actually pretty reasonable. The only problem is that it's a bit inefficient. You would be better off passing a const FooBar& instead of passing FooBar by value.

Your struct FooBar solution is even better than partial classes, because you can specify exactly which members the callback should have access to.

Edit: Reading your struct FooBar solution more closely, I see you're thinking of tediously copying the members individually before passing them to the callback. You can skip all that just by putting a FooBar object in your Foo class, like so:

struct FooBar {
    string name;
};

typedef void(*fptr)(const FooBar&);

class Foo {
public:
    Foo(string name, fptr function);
    void activate()
    {
        m_function(data);
    }
private:
    FooBar data;
    fptr m_function;
};

It's worth pointing out that, with this solution, the callbacks cannot access m_function, unless you decide to put it in FooBar. This is what I meant when I said that you can specify exactly which members the callback should have access to.

Rick Yorgason
  • 1,616
  • 14
  • 22
0

I could just ignore encapsulation and create a setter and getter for name and all other properties that might be needed by the function in the future. This is pretty terrible solution, I should basically create setter and getter for everything there is in my class, since the function can do anything to my object.

True - this is basically making implementation details public (and in most cases, not something you should do).

An other solution would be a struct that holds the required properties inside it:

[...] But this is not much different from not using encapsulation, and it doesn't seem like a too good solution either. What would be the best approach for this problem?

Actually it is very different. Consider that you are actually calling an external function with normal parameters:

struct EventData { string name, yadayada; }

class Foo
{
public:
    void activate()
    {
        m_function( EventData(m_name, yadayada) );
    }
};

This is not accessing private data (Foo accesses it's own private data, m_function accesses it's own parameter values), but dependency injection.

There are no architecture compromises with this approach.

Community
  • 1
  • 1
utnapistim
  • 26,809
  • 3
  • 46
  • 82