52
votes

If I have two different constant members variables, which both need to be initialized based on the same function call, is there a way to do this without calling the function twice?

For example, a fraction class where numerator and denominator are constant.

int gcd(int a, int b); // Greatest Common Divisor
class Fraction {
public:
    // Lets say we want to initialize to a reduced fraction
    Fraction(int a, int b) : numerator(a/gcd(a,b)), denominator(b/gcd(a,b))
    {

    }
private:
    const int numerator, denominator;
};

This results in wasted time, as the GCD function is called twice. You could also define a new class member, gcd_a_b, and first assign the output of gcd to that in the initializer list, but then this would lead to wasted memory.

In general, is there a way to do this without wasted function calls or memory? Can you perhaps create temporary variables in an initializer list?

3
Do you have proof that "the GCD function is called twice"? It's mentioned twice, but that is not the same thing as a compiler emitting code that calls it twice. A compiler may deduce that it is a pure function and reuse its value at the second mention.Eric Towers
@EricTowers: Yes, compilers can sometimes work around the problem in practice for some cases. But only if they can see the definition (or some annotation in an object), otherwise no way to prove it's pure. You should compile with link-time optimization enabled, but not everyone does. And the function might be in a library. Or consider the case of a function that does have side-effects, and calling it exactly once is a matter of correctness?Peter Cordes
@EricTowers Interesting point. I did actually attempt to check it by putting a print statement inside the GCD function, but now I realise that that would prevent it from being a pure function.Qq0
@Qq0: You can check by looking at the compiler generated asm, e.g. using the Godbolt compiler explorer with gcc or clang -O3. But probably for any simple test implementation it would actually inline the function call. If you use __attribute__((const)) or pure on the prototype without providing a visible definition, it should let GCC or clang do common-subexpression elimination (CSE) between the two calls with the same arg. Note that Drew's answer works even for non-pure functions so it's much better and you should use it any time the func might not inline.Peter Cordes
Generally, non-static const member variables are best avoided. One of the few areas where const everything doesn't often apply. For instance you can't assign class objects. You can emplace_back into a vector but only so long as the capacity limit doesn't kick in a resizing.doug

3 Answers

69
votes

In general, is there a way to do this without wasted function calls or memory?

Yes. This can be done with a delegating constructor, introduced in C++11.

A delegating constructor is a very efficient way to acquire temporary values needed for construction before any member variables are initialized.

int gcd(int a, int b); // Greatest Common Divisor

class Fraction {
public:
    // Call gcd ONCE, and forward the result to another constructor.
    Fraction(int a, int b) : Fraction(a,b,gcd(a,b))
    {
    }
private:
    // This constructor is private, as it is an
    // implementation detail and not part of the public interface.
    Fraction(int a, int b, int g_c_d) : numerator(a/g_c_d), denominator(b/g_c_d)
    {
    }
    const int numerator, denominator;
};
10
votes

The member vars are initialized by the order they are declared in the class declaration, hence you can do the following (mathematically)

#include <iostream>
int gcd(int a, int b){return 2;}; // Greatest Common Divisor of (4, 6) just to test
class Fraction {
public:
    // Lets say we want to initialize to a reduced fraction
    Fraction(int a, int b) : numerator{a/gcd(a,b)}, denominator(b/(a/numerator))
    {    
    }
//private:
    const int numerator, denominator;//make sure that they are in this order
};
//Test
int main(){
    Fraction f{4,6};
    std::cout << f.numerator << " / " << f.denominator;
}

No need for calling another constructors or even making them.

-3
votes

@Drew Dormann gave a solution similar to what I had in mind. Since OP never mentions not being able to modify the ctor, this can be called with Fraction f {a, b, gcd(a, b)}:

Fraction(int a, int b, int tmp): numerator {a/tmp}, denominator {b/tmp}
{
}

Only this way there is no second call to a function, constructor or otherwise, so it's no wasted time. And it's no wasted memory since a temporary would have to be created anyway, so you may as well make good use of it. It also avoids an extra division.