Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Const function calling non const or vice versa (to avoid duplication)? [duplicate]

Is there any advantage using one over the other:

class Foo
{
public:
    const int& get() const
    {
        // stuff here
        return myInt;
    }

    int& get()
    {
        return const_cast<int&>(static_cast<const Foo*>(this)->get());
    }
};

Or

class Foo
{
public:
    int& get()
    {
        // stuff here
        return myInt;
    }

    const int& get() const
    {
        return const_cast<Foo*>(this)->get();
    }
};

I only used the first one, but I just saw the second one used somewhere, so I am wondering.

The comment // stuff here could be a non-trivial check like retrieving the index of a table in order to return a ref on a member of the table (for example: myInt = myTable[myComputedIndex];) so I cannot just make it public. Thus table and any member are not const.

like image 562
Silouane Avatar asked Jun 26 '17 08:06

Silouane


People also ask

Can a const function call a non-const function?

const member functions may be invoked for const and non-const objects. non-const member functions can only be invoked for non-const objects. If a non-const member function is invoked on a const object, it is a compiler error.

What will happen if a const object calls a non-const member function?

If the function is non-constant, then the function is allowed to change values of the object on which it is being called. So the compiler doesn't allow to create this chance and prevent you to call a non-constant function on a constant object, as constant object means you cannot change anything of it anymore.

How is it possible to have both const and non-const version of a function?

There are legitimate uses of having two member functions with the same name with one const and the other not, such as the begin and end iterator functions, which return non-const iterators on non-const objects, and const iterators on const objects, but if it's casting from const to do something, it smells like fish.

What are const methods?

The const member functions are the functions which are declared as constant in the program. The object called by these functions cannot be modified. It is recommended to use const keyword so that accidental changes to object are avoided. A const member function can be called by any type of object.


4 Answers

If you have to make a function that is const-agnostic, and avoids duplication, one neat way to do it is delegating implementation to a template, for example

class Foo {
private: 

    int my_int;
    template <typename ThisPtr>
    static auto& get(ThisPtr this_ptr) { 
        return this_ptr->my_int;
    }

public:
    int& get() {
        return get(this);
    }

    const int& get() const {
        return get(this);
    }
};

This way you are free from the fear associated with using const_cast, mutable and other stuff that goes into trying to reduce code duplication in cases like this. If you get something wrong, the compiler will let you know.

like image 88
Curious Avatar answered Oct 12 '22 16:10

Curious


Ignoring the issue of whether you really need a getter, the best solution when duplicating functionality in both a const and non-const method is to have the non-const method call the const method and cast away the const-ness of the result (i.e. the first of the two alternatives you present in the question).

The reason is simple: if you do it the other way around (with the logic in the non-const method), you could accidentally end up modifying a const object, and the compiler won't catch it at compile time (because the method is not declared const) - this will have undefined behaviour.

Of course this is only a problem if the "getter" is not actually a getter (i.e. if it is doing something more complicated than just returning a reference to a private field).

Also, if you are not constrained to C++11, the template-based solution presented by Curious in their answer is another way of avoiding this problem.

like image 21
davmac Avatar answered Oct 12 '22 16:10

davmac


Is there any advantage using one over the other: ...

No, both are bad because they violate the data encapsulation principle.

In your example you should rather make myInt a public member. There's no advantage to have getters for such case at all.

If you really want (need) getter and setter functions these should look like this:

class Foo
{
private: 
    mutable int myInt_;
 // ^^^^^^^ Allows lazy initialization from within the const getter,
 //         simply omit that if you dont need it.

public:
    void myInt(int value)
    {
        // Do other stuff ...
        myInt = value;
        // Do more stuff ...
    }

    const int& myInt() const
    {
        // Do other stuff ...
        return myInt_;
    }
}
like image 2
πάντα ῥεῖ Avatar answered Oct 12 '22 16:10

πάντα ῥεῖ


You don't say where myInt comes from, the best answer depends on that. There are 2+1 possible scenarios:

1) The most common case is that myInt comes from a pointer internal to the class.

Assuming that, this is the best solution which avoids both code duplication and casting.

class Foo{
    int* myIntP;
    ... 
    int& get_impl() const{
       ... lots of code
       return *myIntP; // even if Foo instance is const, *myInt is not
    }
public:
    int& get(){return get_impl();}
    const int& get() const{return get_impl();}
};

This case above applies to pointer array, and (most) smart pointers.

2) The other common case is that myInt is a reference or a value member, then the previous solution doesn't work. But it is also the case where a getter is not needed at all. Don't use a getter in that case.

class Foo{
     public:
     int myInt; // or int& myInt;
};

done! :)

3) There is a third scenario, pointed by @Aconcagua, that is the case of an internal fixed array. In that case it is a toss-up, it really depends what you are doing, if finding the index is really the problem, then that can be factored away. It is not clear however what is the application:

class Foo{
    int myInts[32];
    ... 
    int complicated_index() const{...long code...}
public:
    int& get(){return myInts[complicated_index()];}
    const int& get() const{return myInts[complicated_index()];}
};

My point is, understand the problem and don´t over engineer. const_cast or templates are not needed to solve this problem.


complete working code below:

class Foo{
    int* myIntP;
    int& get_impl() const{
       return *myIntP; // even if Foo instance is const, *myInt is not
    }
public:
    int& get(){return get_impl();}
    const int& get() const{return get_impl();}

    Foo() : myIntP(new int(0)){}
    ~Foo(){delete myIntP;}
};

#include<cassert>

int main(){
    Foo f1; 
    f1.get() = 5;
    assert( f1.get() == 5 );

    Foo const f2;
//    f2.get() = 5; // compile error
    assert( f2.get() == 0 );    
    return 0;
}
like image 1
alfC Avatar answered Oct 12 '22 16:10

alfC