Как правильно писать перегрузки R-значения для операторов
Для контекста фактический класс, с которым я работаю, значительно сложнее и больше, чем я здесь показываю, но я использую это как пример.
struct Vector {
int x, y;
Vector() : Vector(0,0) {}
Vector(int x, int y) : x(x), y(y) {}
};
Я хотел бы добавить перегрузки оператора, чтобы добавить и вычесть Vector
друг друга.
Vector& operator+=(Vector const& v) {
x += v.x;
y += v.y;
return *this;
}
Vector operator+(Vector const& v) const {
return Vector(*this) += v;
}
Vector& operator-=(Vector const& v) {
x -= v.x;
y -= v.y;
return *this;
}
Vector operator-(Vector const& v) const {
return Vector(*this) -= v;
}
Однако этот код может допускать неудачные построения:
int main() {
Vector & a = Vector(1,2) += Vector(5,4);//This compiles and invokes undefined behavior!
std::cout << a.x << ',' << a.y << std::endl;//This isn't safe!
}
Итак, я переписал код, чтобы помнить, является ли объект L-значением или R-значением:
Vector& operator+=(Vector const& v) & {
x += v.x;
y += v.y;
return *this;
}
Vector&& operator+=(Vector const& v) && {
return std::move(*this += v);
}
Vector operator+(Vector const& v) const {
return Vector(*this) += v;
}
Vector& operator-=(Vector const& v) & {
x -= v.x;
y -= v.y;
return *this;
}
Vector&& operator-=(Vector const& v) && {
return std::move(*this -= v);
}
Vector operator-(Vector const& v) const {
return Vector(*this) -= v;
}
Итак, мой оставшийся вопрос заключается в том, что, хотя этот код компилируется и выполняет то, что я ожидаю, этот код безопасен и не содержит неожиданных Undefined Поведение?
int main() {
//No Longer compiles, good.
//Vector & a = Vector(1,2) += Vector(5,4);
//Is this safe?
Vector b = Vector(1,2) += Vector(5,4);
//Other cases where this code could be unsafe?
}
Ответы
Ответ 1
Вот относительно стандартные способы перегрузки этих операторов:
Vector& operator+=(Vector const& v)& {
x += v.x;
y += v.y;
return *this;
}
friend Vector operator+(Vector lhs, Vector const& v) {
lhs+=v;
return std::move(lhs); // move is redundant yet harmless in this case
}
Vector& operator-=(Vector const& v)& {
x -= v.x;
y -= v.y;
return *this;
}
friend Vector operator-(Vector lhs, Vector const& v) {
lhs -= v;
return std::move(lhs); // move is redundant yet harmless in this case
}
обратите внимание на то, что в строке из многих +
или -
приведенное выше генерирует меньше копий и больше ходов, чем ваши перегрузки.
a+b+c
становится (a+b)+c
, а возвращаемое значение a+b
направляется непосредственно в аргумент lhs +c
. И первая строка вашего +
заключалась в создании копии так или иначе, поэтому дополнительная копия в подписи безобидна.
Если у вас нет веской причины, запретите +=
и =
по rvalues. int
не поддерживает его, вы тоже не должны.
Ответ 2
Если вы сомневаетесь, делайте это как int.
Можете ли вы выполнить сложное назначение на int
rvalues? Конечно нет. Итак, зачем беспокоиться о своем Vector
?
Ваш b
безопасен, но Vector&& c = Vector(1,2) += Vector(5,4);
нет. Обычным исправить является возврат по значению, но возврат по значению из оператора присваивания также несколько странный.
Ответ 3
Перемещение - плохая идея.
Vector&& operator-=(Vector const& v) && {
return std::move(*this -= v);
}
Вы можете легко завершить перемещенный объект, не ожидая этого.
В основном:
int main() {
Vector & a = Vector(1,2) += Vector(5,4);//This compiles and invokes undefined behavior!
std::cout << a.x << ',' << a.y << std::endl;//This isn't safe!
}
Почему бы просто не использовать
Vector a = Vector(1,2) + Vector(5,4)
Что касается правильного использования ссылок r-значения в перегрузках операторов, вы можете использовать их только в +, а не в + =. См. std::string operator + и operator + =