0

I have a data-structure class in C++ with an accessor to some object (may be large) and I have const and non-const methods using this accessor so I need to overload it. I am looking for a critique of the code below - maybe there is a way to accomplish the same thing that is cleaner?

The way I understand it, there are two ways to achieve this without duplicating the code in the accessor in the following case, the method get(). I am not sure if there are serious issues with either of these two methods and I would like some guidance here.

I like method A because:

  • only one const_cast
  • const-version of the method get() returns a copy
  • the non-const method gets the non-const reference directly

I don't like method A because:

  • the non-const method get() is const only by contract, (not checked by compiler)
  • harder to get a const-reference, though not impossible

I like method B because:

  • the const-ness of the const method get() is checked by compiler
  • copy of the returned object is controlled by the user

I don't like method B because:

  • requires two const_casts which is hard to read

here is the (minimal) example code of the two cases.

/**
 * summary:
 * Two classes with an overloaded method which is
 * guaranteed (by contract) not to change any
 * internal part of the class. However, there is a
 * version of this method that will return a non-const
 * reference to an internal object, allowing the user
 * to modify it. Don't worry about why I would ever
 * want to do this, though if you want a real-world
 * example, think about std::vector<>::front()
 *
 * The difference between A and B can be summarized
 * as follows. In both cases, the second method merely
 * calls the first, wrapped with the needed
 * const_cast's
 *
 * struct A {
 *     int& get();
 *     int  get() const;
 * };
 *
 * struct B {
 *     const int& get() const;
 *           int& get();
 * };
 *
 **/

struct A
{
    int _val;

    A() : _val(7) {};

    // non-const reference returned here
    // by a non-const method
    int& get()
    {
        // maybe lots of calculations that you do not
        // wish to be duplicated in the const version
        // of this method...
        return _val;
    }

    // const version of get() this time returning
    // a copy of the object returned
    int get() const
    {
        // CONST-CAST!!?? SURE.
        return const_cast<A*>(this)->get();
    }

    // example of const method calling the
    // overloaded get() method
    int deep_get() const
    {
        // gets a copy and makes
        // a copy when returned
        // probably optimized away by compiler
        return this->get();
    }
};

struct B
{
    int _val;

    B() : _val(7) {};

    // const reference returned here
    // by a const method
    const int& get() const
    {
        // maybe lots of calculations that you do not
        // wish to be duplicated in the non-const
        // version of this method...
        return _val;
    }

    // non-const version of get() this time returning
    // a copy of the object returned
    int& get()
    {
        // CONST-CAST!? TWO OF THEM!!?? WHY NOT...
        return const_cast<int&>(const_cast<const B*>(this)->get());
    }

    // example of const method calling the
    // overloaded get() method
    int deep_get() const
    {
        // gets reference and makes
        // a copy when returned
        return this->get();
    }
};


int main()
{
    A a;
    a.get() = 8;  // call non-const method
    a.deep_get(); // indirectly call const method

    B b;
    b.get() = 8;  // call non-const method
    b.deep_get(); // indirectly call const method
}
John
  • 1,709
  • 1
  • 24
  • 27
  • 1
    If your code has no problems (that you are aware of), then this probably belongs on http://codereview.stackexchange.com/ – Benjamin Lindley Jun 05 '13 at 21:17
  • Ah, didn't know that site existed, thanks! I just created the same question on codereview. http://codereview.stackexchange.com/questions/27064/overloaded-const-and-non-const-class-methods-returning-references-in-c – John Jun 05 '13 at 21:40
  • Voted to close here as it has been cross posted to code review. http://codereview.stackexchange.com/questions/27064/overloaded-const-and-non-const-class-methods-returning-references-in-c/27161#27161 – Martin York Jun 08 '13 at 17:37

1 Answers1

2

Constness of a member function should be decided based on the question: is the member function used in a context to modify the object? (either the member modifies the object directly, or returns a reference/pointer to internal data so that outside callers can modify the object). If yes, make it non-const, otherwise make it const.

The compiler will correctly choose between overloads that differ on constness alone. However, the return type is not used in overload resolution. Furthermore, the return by-value/by-reference should be decided only on the expected cost and the intended ownership of what you are going to return. Fortunately, C++11 makes life easier by providing move semantics. This means that you can happily return large data structures by-value. Only return by reference if the referenced object will outlive the outside caller.

It seems to me that your int& get() should be renamed void set(int) and that you could split your int get() const into a computation helper and a proper get()

class C
{
    int val_;
public:
    void modify()   { /* some expensive computation on val_ */ }
    int get() const { return val_; }
    void set(int v) { val_ = v_; }
};

Alternatively, if you want to keep to get() functions, you could do

class D
{
int val_;
public:
    void modify()    { /* some expensive computation on val_ */ }
    int get() const  { modify(); return val_; }
    int& get()       { modify(); return val_; } // no const-cast needed
};
Community
  • 1
  • 1
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Maybe my code was too minimal. Imagine get() is really get(string) and there is a lot of (potentially recursive) computation that is done with the string to determine exactly which object to return. I am looking to return a reference to the (public) member object but also be able to call the same (or overloaded) method from a const method. – John Jun 05 '13 at 21:49
  • 1
    @Johann same thing: write a helper function that does the computation, and write 2 `get()` functions to return the object state that each call the internal computation helper. – TemplateRex Jun 05 '13 at 21:51