Jonathan Boccara's blog

Should Private Methods Always Be Const?

Published June 14, 2019 - 0 Comments

Private methods

What is the job of a function?

A function takes inputs and computes outputs based on them. And to clarify a function’s interface, we saw how important it was to clarify what its inputs and outputs are.

There is a particular sort of function in C++ that use specific conventions to express their inputs and outputs: the private methods of a class, which are useful to organize the implementation of a class into sub-routines.

Indeed, private methods have access to the data members of a class so, in theory, a private method could take any member of a class as input or output, without them showing in its prototype.

How to keep control of those methods that don’t show their inputs and outputs? And is there a need at all to have that kind of control?

For the code examples, let’s use a class A that has several private data members and a private method, astutely called doSomething.

In its header we have:

// a.hpp

class A
{
public:
    void publicMethod();
private:
    Data1 member1;
    Data2 member2;
    Data3 member3;
    Data4 member4;
    Data5 member5;
    Data6 member6;

    void doSomething();
};

And its implementation file:

// a.cpp

void A::publicMethod()
{
   // some code..

   doSomething(); // oops, what was the impact on the members?

   // more code...
}

The problem with this code is that from the perspective of the publicMethod, we have no idea what side-effects the call to the private method doSomething had.

Let’s see how to clarify this situation.

Moving some code out of the class

We already know how to be clear about a function’s inputs and outputs. So one way to be clear about the inputs of a private method too is to… remove it, and replace it by a free function! This free function would be outside of the class, but in the same implementation file:

// a.cpp

namespace
{
Data4 doSomething(Data1 const& data1, Data5 const& data5)
{
    // code that used to be in privateMethod
}
}

void A::publicMethod()
{
   // some code..

   member4 = doSomething(member1, member5); // we now see which parts of the class are impacted

   // more code...
}

This new free function doesn’t act directly on the data members of class A. Instead, A calls it by passing in its data members, and then acts on other data members based on the value returned.

The advantage of this operation is that from the perspective of publicMethod, it is now very clear that the call to the functions uses member1 and member5, and only impacts member4. This clarifies the inputs and outputs of what used to be the private method.

Don’t tear the class to shreds

In some cases, for example when the private methods involves many members of the class, this technique becomes less practical:

// a.cpp

namespace
{

struct Outputs
{
    Data2 data2;
    Data4 data4;
};

Outputs doSomething(Data1 const& data1, Data3 const& data3, Data5 const& data5, Data6 const& data6)
{
    // code that used to be in the private method
}
}

void A::publicMethod()
{
   // some code..

   auto outputs = doSomething(data1, data3, data5, data6);
   member2 = outputs.data2;
   member4 = outputs.data4;

   // more code...
}

Wow, in this sort of case, using a free function generates a lot more code than the call to a private method.

It could be mitigated by using tuples:

// a.cpp

namespace
{

std::tuple<Data2, Data4> doSomething(Data1 const& data1, Data3 const& data3, Data5 const& data5, Data6 const& data6)
{
    // code that used to be in privateMethod
}
}

void A::publicMethod()
{
   // some code..

   std::tie(member2, member4) = doSomething(data1, data3, data5, data6);

   // more code...
}

But still, that’s a pretty bulky function call.

So even if extracting a private method into a free function can be convenient in some cases, it’s not always the best option.

At least, be very clear about the outputs

When you think about it, what exactly was the problem with the initial call to the private method?

// a.cpp

void A::publicMethod()
{
   // some code..

   doSomething(); // what was the impact on the members?

   // more code...
}

After its call, we have little indication about what was modified in the class. And this is what matters.

Indeed, do we know exactly what the inputs of this method are? Not exactly, but we know for sure that they are part of the data members (unless the code uses global variables, which is a separate issue). This is a reasonable amount of information, that we derive from the very fact that it’s a method of the class.

But as to the side-effects of the private method, we need to know them very precisely, to follow what is going on during the execution of the publicMethod.

A convention

One way to do this is to agree on a convention, that has two sides:

  • the private method is allowed to access any data member of the class, but not to modify them,
  • the members to be modified should be passed in as method parameters, as non-const references.

This way, the call site of the private method shows what data are impacted by this call:

// a.cpp

void A::doSomething(Data2& data2, Data4& data4)
{
    // code that modifies data2 and data4...
}

void A::publicMethod()
{
   // some code..

   doSomething(member2, member4); // we know only member2 and member4 are impacted

   // more code...
}

With the above convention, this piece of code expresses that the private method only modifies data2 and data4.

But… outputs should not be passed in as references, right?

We saw that outputs should come out of a function via its return type, and not be passed in as a non-const reference. So is our guideline of passing modified members by non-const reference in contradiction with this principle?

In fact, from the perspective of the private method, the members that it modifies are not outputs. Indeed, if they were outputs, the method would create them and return them.

Rather, since the method modifies the data members, they can be seen as input too, since the function uses them in a way, by modifying them. So those members rather play a role of input-outputs rather than just outputs. And we saw that the convention in C++ to express input-outputs was to use… non-const references. So no contradiction here.

Should private methods be const?

If you agree about the convention of forcing a private method to use its parameters to modify class data, how can we enforce it?

There is a simple way: the private method can be const. This way, it can’t modify a data member by accident, but it can still read from the data members and use them as inputs.

void A::doSomething(Data2& data2) const // no silent access to members
{
   // code that modifies data2
}

void A::publicMethod() // not const
{
   // some code..

   doSomething(member2); // this modifies member2

   // more code...
}

But on the other hand, having a const method expresses that calling it won’t change the data of the class. And here we use it to do just that, so that can be off-putting.

My take is that we should either use that convention AND also convene that in this case the const is a technical artifact to enforce it, or not use the const here. In this case we’d rely on manual enforcement of the convention, where everyone would be careful not to modify data members directly from a private method.

Do you have an opinion on this convention, or on the way to enforce it?

Related articles:

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin