DawidPi
DawidPi

Reputation: 2365

casting operator() - cast to reference and cast to value

My Concern is touching the subject given in the title.

Suppose we have got some very simple class

class Integer{
public:
    Integer(int number) : m_value(number) {}
    int value(){return m_value;}
private:
    int m_value;
};

Now we can use this class, that represents int, of course there is no bigger sense in using this class, but it's just an example. If want this class to behave more like a real integer, then we should provide casting operators (not mentioning operator ==, >, < etc.) and here is my concern, because I can define two operators:

operator T() {return m_value;}
operator T&() {return m_value;}

some easiest examples compiles and works as such with both of them (first or second), so our class can be as follows:

class Integer{
public:
    Integer(int number) : m_value(number) {}
    int value(){return m_value;}
    operator T(){return m_value;} // casting operator
private:
    int m_value;
};

But it can be:

class Integer{
public:
    Integer(int number) : m_value(number) {}
    int value(){return m_value;}
    operator T&(){return m_value;} // casting operator
private:
    int m_value;
};

As well, so my question is which of those two are appropriate? Of course I cannot have both of them, because compiler would not be able to recongnize which one to use.

Naturally reference casting seems to be more effective, but I am not sure if it would work always the way it should work. Could please advice me somehow and say, why one operator is better than another?

Upvotes: 4

Views: 333

Answers (3)

Harajyoti Das
Harajyoti Das

Reputation: 731

I agree with both the answers above. One should always prefer to minimize the number of functions that directly manipulate the representation of an object and also exposing the same by reference(why is explained in the above two answers and in comments as well).When it is really necessary to access the representation,such as operator+=,then that can be achieved by inherently modifying the value of their first argument in the class itself.

Operators that simply produce a new value based on the values of its arguments, such as+, can be defined outside the class and use the essential operators in their implementation.

class complex{
     double re,im;
     public:
     complex& operator+=(complex a);//needs access to representation.

};
complex operator+(complex a,complex b)
{
  complex r=a;
  return r+=b; //access representation through +=
}

Now coming to your operator,type cast, Do you really require to access representation? First of all, when do we require to implement type cast? For a simple example :

class Cents
{
private:
    int m_nCents;
public:
    Cents(int nCents=0)
    {
        m_nCents = nCents;
    }
};


class Dollars
    {
    private:
        int m_nDollars;
    public:
        Dollars(int nDollars=0)
        {
            m_nDollars = nDollars;
        }

         // Allow us to convert Dollars into Cents
         operator Cents() { return Cents(m_nDollars * 100); }
    };

According to me, here we don't need access to representation neither any need to return by reference.

All the examples above deals with a object which is very small in size.But when dealing with heavy object where copy operation indeed can be very costly, it is up to programmer to decide.If we want to avoid copy cost we can pass reference and return reference but...

  1. A reference to the result will be passed out of the function as a reference to the return value, the return value cannot be an automatic variable.

  2. Since an operator is often used more than once in an expression, the result cannot be as static local variable.

So,The result would typically be allocated on the free store.Copying the return value is often cheaper (in execution time, code space, and data space) than allocating and (eventually) deallocating an object on the free store.(#11.6 over.large #C++ Programming Language).

These topics are always debatable but what we need to make sure is about security and no leaks which leads to more control and less pain in future.

Upvotes: 3

YSC
YSC

Reputation: 40070

As a matter of fact, you need both and with a bit of tweaking, the compiler will know which to choose. All the magic is in the fact that you can (and should, when it makes sense) define a member function to be const, id est being callable from a constant object.

Hence, your Integer class should look like

class Integer
{
public:
    Integer() : Integer(0) {}
    Integer(int number) : m_value(number) {}
    Integer(const Integer& other) : m_value(other.value()) {}
    int value() const { return m_value; }

    operator int() const { return m_value; }
    operator int&() { return m_value; }
    const Integer& operator=(const Integer& other) { m_value = other; return *this; }
private:
    int m_value;
};

This definition would allow the Integer class to be usable in those following cases:

#include <iostream>
int main()
{
    const Integer zero;
    const Integer one(1);
    Integer counter;

    counter = zero + one;
    std::cout << "counter: " << counter << std::endl;

    counter++;
    std::cout << "counter: " << counter << std::endl;

    for (Integer i=0 ; i < counter ; ++i)
    {
        std::cout << "i: " << i << std::endl;
    }

    return zero;
}

Compile & run: g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

Output:

counter: 1
counter: 2
i: 0
i: 1

Live example: http://coliru.stacked-crooked.com/a/641fe02e70c02920

Upvotes: 4

SergeyA
SergeyA

Reputation: 62563

Reference casting as defined in your example is rather useless - you return a modifiable reference to your inner member, and as such, violate the encapsulation principle.

If you return const reference instead, this will not be the issue. As to the choice between int and const int& it is really not important - they would behave exactly the same in all contexts.

This will be more interesting if you define a conversion to some heavy class. When doing so, you will be presented with two options - return by const reference - but this would mean, it won't be possible to call non-const qualified methods on returned value. If return by value, you will incur costs of copy-constructor. The choice would be dictated by class design.

There is also a question of lifetime of returned references - but this is usually not a problem, unless the code becomes really tricky (you need to store the returned reference as a member of the class to have this problem).

Upvotes: 1

Related Questions