Thursday, February 17, 2011

What is the best signature for overloaded arithmetic operators in C++?

I had assumed that the canonical form for operator+, assuming the existence of an overloaded operator+= member function, was like this:

const T operator+(const T& lhs, const T& rhs)
{
    return T(lhs) +=rhs;
}

But it was pointed out to me that this would also work:

const T operator+ (T lhs, const T& rhs)
{
    return lhs+=rhs;
}

In essence, this form transfers creation of the temporary from the body of the implementation to the function call.

It seems a little awkward to have different types for the two parameters, but is there anything wrong with the second form? Is there a reason to prefer one over the other?

From stackoverflow
  • With the edited question, the first form would be preferred. The compiler will more likely optimize the return value (you could verify this by placing a breakpoint in the constructor for T). The first form also takes both parameters as const, which would be more desirable.

    Research on the topic of return value optimization, such as this link as a quick example: http://www.cs.cmu.edu/~gilpin/c++/performance.html

    Corey Ross : So does the second form.
    Jesse Beder : Not necessarily. Most modern compilers will detect that only one copy is necessary and optimize it away.
    Jesse Beder : (The above is intended @Kris, but actually applies to @Corey also.)
    Kris Kumler : Corey: See Scott Meyer's More Effective C++, Item 20.
    Corey Ross : @Kris K. I will look that up when I get home.
    Kris Kumler : I've worked with compilers recently that, when they did support the optimization, you had to make sure that it was turned on.
    Corey Ross : RVO, right on. There is also (named) NRVO which should make both cases have very similar generated code (verify with your compiler!). http://blogs.msdn.com/slippman/archive/2004/02/03/66739.aspx http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx I think this is what Jesse is getting at.
    JohnMcG : I fixed the fist version to increase the likelihood that RVO would work.
  • I'm not sure if there is much difference in the generated code for either.

    Between these two, I would (personally) prefer the first form since it better conveys the intention. This is with respect to both your reuse of the += operator and the idiom of passing templatized types by const&.

  • I would prefer the first form for readability.

    I had to think twice before I saw that the first parameter was being copied in. I was not expecting that. Therefore as both versions are probably just as efficient I would pick them one that is easier to read.

    Derek Park : Ditto. I was staring at the second example wondering how it could possibly be equivalent, and then I saw that it wasn't being passed by reference. The first version is much more clear.
  • const T operator+(const T& lhs, const T& rhs)
    {
        return T(lhs)+=rhs;
    }
    

    why not this if you want the terseness?

    JohnMcG : Thanks -- and this is what's in the Myers book to eliminate the temporary -- I've adjusted it.
  • My first thought is that the second version might be infinitessimally faster than the first, because no reference is pushed on the stack as an argument. However, this would be very compiler-dependant, and depends for instance on whether the compiler performs Named Return Value Optimization or not.

    Anyway, in case of any doubt, never choose for a very small performance gain that might not even exist and you more than likely won't need -- choose the clearest version, which is the first.

  • Actually, the second is preferred. As stated in the c++ standard,

    3.7.2/2: Automatic storage duration

    If a named automatic object has initialization or a destructor with side effects, it shall not be destroyed before the end of its block, nor shall it be eliminated as an optimization even if it appears to be unused, except that a class object or its copy may be eliminated as specified in 12.8.

    That is, because an unnamed temporary object is created using a copy constructor, the compiler may not use the return value optimization. For the second case, however, the unnamed return value optimization is allowed. Note that if your compiler implements named return value optimization, the best code is

    const T operator+(const T& lhs, const T& rhs)
    {
        T temp(lhs);
        temp +=rhs;
        return temp;
    }
    
  • I think that if you inlined them both (I would since they're just forwarding functions, and presumably the operator+=() function is out-of-line), you'd get near indistinguishable code generation. That said, the first is more canonical. The second version is needlessly "cute".

    Drew Hall : Whoa--didn't realize how old this question was--hope you're still interested! :)

0 comments:

Post a Comment