简体   繁体   中英

How to enforce RVO for operator return value?

How to enforce RVO in the last 3 operators:

#include <iostream>

class Noisy {
    private:
        int m_value;
    public:
        Noisy(int value = 0): m_value(value) 
        {
            std::cout << "Noisy(int)\n";
        }
        Noisy(const Noisy& other): m_value(other.m_value)  
        { 
            std::cout << "Noisy(const Noisy&)\n";
        }
        Noisy(Noisy&& other): m_value(other.m_value)
        { 
            std::cout << "Noisy(Noisy&&)\n";
        }
        //~Noisy() {
        //    std::cout << "dtor\n";
        //}
        Noisy operator+(const Noisy& rhs) &
        {
            std::cout << "+(const Noisy&)&\n";
            return Noisy(m_value + rhs.m_value);
        }
        Noisy operator+(Noisy&& rhs) &
        {
            std::cout << "+(Noisy&&)&\n";
            rhs.m_value += m_value;
            return rhs; //std::move(rhs);
        }
        Noisy operator+(const Noisy& rhs) &&
        {
            std::cout << "+(const Noisy&) &&\n";
            this->m_value += rhs.m_value;
            return *this; //std::move(*this);
        }

        Noisy operator+(Noisy&& rhs) &&
        {
            std::cout << "+(Noisy&&) &&\n";
            this->m_value += rhs.m_value;
            return *this; //std::move(*this);
        }
};

int main()
{
    Noisy a, b, c, d, e, f, g;
    Noisy z = a + b + c + d + e + f + g;

    return 0;
}

The program run output:

Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
+(const Noisy&)&
Noisy(int)
+(const Noisy&) &&
Noisy(const Noisy&)
+(const Noisy&) &&
Noisy(const Noisy&)
+(const Noisy&) &&
Noisy(const Noisy&)
+(const Noisy&) &&
Noisy(const Noisy&)
+(const Noisy&) &&
Noisy(const Noisy&) 

or when explicitly using std::move in the last three operators:

Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
+(const Noisy&)&
Noisy(int)
+(const Noisy&) && 
Noisy(Noisy&&)
+(const Noisy&) &&
Noisy(Noisy&&)
+(const Noisy&) &&
Noisy(Noisy&&)
+(const Noisy&) &&
Noisy(Noisy&&)
+(const Noisy&) &&
Noisy(Noisy&&) 

I want no copying in the operators, something like this:

Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
+(const Noisy&)&
Noisy(int)
+(const Noisy&) &&
+(const Noisy&) &&
+(const Noisy&) &&
+(const Noisy&) &&
+(const Noisy&) &&

The only way i figured so far is to return a reference from the operators but this clearly would result in a dangling reference.

I compile in c++14 and c++17 with fresh g++.

Update

I understood that it is not possible to force the compiler to do what i want without breaking rules.
But what ptevents the compiler to optimize rvalues locally? I imagine it can create a single rvalue during the first addition which is modified in the next additions and then assigned to the result variable.

Short Answer

Make your rvalue-reference-caller + const-reference argument operator return an rvalue-reference that is std::move ed out of *this .

Noisy&& operator+(const Noisy& rhs) &&
{
    std::cout << "+(const Noisy&)&&\n";
    m_value += rhs.m_value;
    return std::move(*this);
}

Since this is being fired from an rvalue-reference, you can freely abuse, and move, *this down to the caller where they are free to do whatever they please with it.

Example

#include <iostream>

class Noisy {
private:
    int m_value;
public:
    Noisy(int value = 0) : m_value(value)
    {
        std::cout << "Noisy()\n";
    }
    Noisy(const Noisy& other) : m_value(other.m_value)
    {
        std::cout << "Noisy(const Noisy&)\n";
    }
    Noisy(Noisy&& other) : m_value(other.m_value)
    {
        std::cout << "Noisy(Noisy&&)\n";
        other.m_value = -1;
    }

    ~Noisy()
    {
        std::cout << "~Noisy() : " << m_value << '\n';
    }

    Noisy operator+(const Noisy& rhs) const &
    {
        std::cout << "+(const Noisy&)&\n";
        return Noisy(m_value + rhs.m_value);
    }

    Noisy&& operator+(const Noisy& rhs) &&
    {
        std::cout << "+(const Noisy&)&&\n";
        m_value += rhs.m_value;
        return std::move(*this);
    }

    Noisy operator+(Noisy&& rhs) const
    {
        std::cout << "+(Noisy&&)\n";
        rhs.m_value += m_value;
        return std::move(rhs);
    }
};

int main()
{
    Noisy a, b, c, d, e, f, g;
    std::cout << "========================\n";

    Noisy z = a + b + c + d + e + f + g;
    std::cout << "========================\n";

    return 0;
}

Output

Noisy()
Noisy()
Noisy()
Noisy()
Noisy()
Noisy()
Noisy()
========================
+(const Noisy&)&
Noisy()
+(const Noisy&)&&
+(const Noisy&)&&
+(const Noisy&)&&
+(const Noisy&)&&
+(const Noisy&)&&
Noisy(Noisy&&)
~Noisy() : -1
========================
~Noisy() : 0
~Noisy() : 0
~Noisy() : 0
~Noisy() : 0
~Noisy() : 0
~Noisy() : 0
~Noisy() : 0
~Noisy() : 0

Notice after the initial construction of a.operator+(b) , the resulting object is propagated by rvalue-reference down the chain. The last operation is the move-construction into z . (thus the -1 in the destructor report).


I had prepared a long description of how your operator chaining makes this work that I was going to post, but in the end it got rather cluttered. Suffice it to say that this:

Noisy z = a.operator+(b).operator+(c).operator+(....

is what you're doing, and you need to get the lhs operand of everything past (b) in the chain to push its rvalue-reference to the next call. The operator I showed will allow that.

Best of luck, I hope I understood your goal.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM