Целесообразно ли перегружать операторы с помощью ref-квалификатора, чтобы предотвратить временные задержки?

Мне просто пришло в голову, что operator+ & Co может работать на this для значений; т.е. дано класс Cможно сделать это:

class C {
    // ...
    C operator-( const C& rhs ) const & {
        C result = *this;
        result -= rhs;
        return result;
    }
    C&& operator-( const C& rhs ) && {
        *this -= rhs;
        return std::move( *this );
    }
};

Это предотвратит копирование, просто изменив временные значения на месте.

Будет ли это работать так, как я ожидал? Это разумная оптимизация или компилятор будет создавать такой же быстрый код?

1 ответ

Решение

Допустим, мы просто завернуть std::string и сделать упрощенную версию operator+:

struct C { 
    std::string val;

    C&& operator+(const C& rhs) && {
        val += rhs.val;
        return std::move(*this);
    }   

    std::string::iterator begin() { return val.begin(); }
    std::string::iterator end() { return val.end(); }
};

С этим, это прекрасно работает:

for (char c : C{"hello"}) { .. }

Выражение range-for продлит срок жизни временного объекта, так что все в порядке. Однако учтите это:

for (char c : C{"hello"} + C{"goodbye"}) { .. }

Мы эффективно имеем:

auto&& __range = C{"hello"}.operator+(C{"goodbye"});

Здесь мы не привязываем временную ссылку. Мы связываем ссылку. Срок жизни объекта не увеличивается, потому что... это не объект. Таким образом, у нас есть свисающая ссылка и неопределенное поведение. Это было бы очень удивительно для пользователей, которые ожидали бы, что это сработает:

for (char c : std::string{"hello"} + std::string{"goodbye"}) { .. }

Вы должны вернуть значение:

C operator+(const C& rhs) && {
    val += rhs.val;
    return std::move(*this);
}

Это решает эту проблему (так как теперь у нас есть временное расширение), и если перемещение ваших объектов дешевле, чем их копирование, это победа.

Другие вопросы по тегам