0

I am starting to code bigger objects, having other objects inside them. Sometimes, I need to be able to call methods of a sub-object from outside the class of the object containing it, from the main() function for example.

So far I was using getters and setters as I learned. This would give something like the following code:

class Object {
public:
    bool Object::SetSubMode(int mode);
    int Object::GetSubMode();
private:
    SubObject subObject;
};

class SubObject {
public:
    bool SubObject::SetMode(int mode);
    int SubObject::GetMode();
private:
    int m_mode(0);
};

bool Object::SetSubMode(int mode) { return subObject.SetMode(mode); }
int Object::GetSubMode() { return subObject.GetMode(); }

bool SubObject::SetMode(int mode) { m_mode = mode; return true; }
int SubObject::GetMode() { return m_mode; }

This feels very sub-optimal, forces me to write (ugly) code for every method that needs to be accessible from outside. I would like to be able to do something as simple as Object->SubObject->Method(param);

I thought of a simple solution: putting the sub-object as public in my object. This way I should be able to simply access its methods from outside. The problem is that when I learned object oriented programming, I was told that putting anything in public besides methods was blasphemy and I do not want to start taking bad coding habits. Another solution I came across during my research before posting here is to add a public pointer to the sub-object perhaps?

How can I access a sub-object's methods in a neat way?

Is it allowed / a good practice to put an object inside a class as public to access its methods? How to do without that otherwise?

Thank you very much for your help on this.

Pierre Ciholas
  • 169
  • 3
  • 12
  • I am not sure if the term *subobject* is appropriate. In general you learn a class is a *subclass* of a class in a context of inheritance. Also you may want to design better: what if I create an object of the `Object` (naming is not good here) and call the method ? Does the default constructor suffice for your application/exercise ? Can you tell us what you are trying to design or what the objective of your exercise is ? Maybe your example is not well chosen, I believe. – Ely Jun 26 '17 at 20:25

2 Answers2

0

The problem with both a pointer and public member object is you've just removed the information hiding. Your code is now more brittle because it all "knows" that you've implemented object Car with 4 object Wheel members. Instead of calling a Car function that hides the details like this:

Car->SetRPM(200);  // hiding

You want to directly start spinning the Wheels like this:

Car.wheel_1.SetRPM(200);  // not hiding! and brittle!
Car.wheel_2.SetRPM(200);

And what if you change the internals of the class? The above might now be broken and need to be changed to:

Car.wheel[0].SetRPM(200);  // not hiding!
Car.wheel[1].SetRPM(200);

Also, for your Car you can say SetRPM() and the class figures out whether it is front wheel drive, rear wheel drive, or all wheel drive. If you talk to the wheel members directly that implementation detail is no longer hidden.

Sometimes you do need direct access to a class's members, but one goal in creating the class was to encapsulate and hide implementation details from the caller.

Note that you can have Set and Get operations that update more than one bit of member data in the class, but ideally those operations make sense for the Car itself and not specific member objects.

Dave S
  • 1,427
  • 1
  • 16
  • 18
0

I was told that putting anything in public besides methods was blasphemy

Blanket statements like this are dangerous; There are pros and cons to each style that you must take into consideration, but an outright ban on public members is a bad idea IMO.

The main problem with having public members is that it exposes implementation details that might be better hidden. For example, let's say you are writing some library:

struct A {
    struct B {
        void foo() {...}
    };
    B b;
};

A a;
a.b.foo();

Now a few years down you decide that you want to change the behavior of A depending on the context; maybe you want to make it run differently in a test environment, maybe you want to load from a different data source, etc.. Heck, maybe you just decide the name of the member b is not descriptive enough. But because b is public, you can't change the behavior of A without breaking client code.

struct A {
    struct B {
        void foo() {...}
    };
    struct C {
        void foo() {...}
    };
    B b;
    C c;
};

A a;
a.c.foo(); // Uh oh, everywhere that uses b needs to change!

Now if you were to let A wrap the implementation:

class A {
public:
    foo() { 
        if (TESTING) {
            b.foo();
        } else {
            c.foo();
        }
private:
    struct B {
        void foo() {...}
    };
    struct C {
        void foo() {...}
    };
    B b;
    C c;
};

A a;
a.foo(); // I don't care how foo is implemented, it just works

(This is not a perfect example, but you get the idea.)

Of course, the disadvantage here is that it requires a lot of extra boilerplate, like you have already noticed. So basically, the question is "do you expect the implementation details to change in the future, and if so, will it cost more to add boilerplate now, or to refactor every call later?" And if you are writing a library used by external users, then "refactor every call" turns into "break all client code and force them to refactor", which will make a lot of people very upset.

Of course instead of writing forwarding functions for each function in SubObject, you could just add a getter for subObject:

const SubObject& getSubObject() { return subObject; }

// ...

object.getSubObject().setMode(0);

Which suffers from some of the same problems as above, although it is a bit easier to work around because the SubObject interface is not necessarily tied to the implementation.

All that said, I think there are certainly times where public members are the correct choice. For example, simple structs whose primary purpose is to act as the input for another function, or who just get a bundle of data from point A to point B. Sometimes all that boilerplate is really overkill.

0x5453
  • 12,753
  • 1
  • 32
  • 61
  • Thank you for your brilliant explanation sir. I understand the pros and cons now. Your solution of getter of subobject is just perfect in my case, I am going to use it! – Pierre Ciholas Jun 26 '17 at 21:21