Текущий консенсус заключается в том, что вы должны сначала реализовать все ваши операторы? =, Которые не создают новые объекты. В зависимости от того, является ли безопасность исключений проблемой (в вашем случае, вероятно, нет) или целью, определение оператора? = Может отличаться. После этого вы реализуете оператор? как свободная функция в терминах оператора? = с использованием передача по значению семантика.
// thread safety is not a problem
class Q
{
double w,x,y,z;
public:
// constructors, other operators, other methods... omitted
Q& operator+=( Q const & rhs ) {
w += rhs.w;
x += rhs.x;
y += rhs.y;
z += rhs.z;
return *this;
}
};
Q operator+( Q lhs, Q const & rhs ) {
lhs += rhs;
return lhs;
}
Это имеет следующие преимущества:
- Только одна реализация логики. Если класс меняется, вам нужно только переопределить операторы? = И операторы? будет адаптироваться автоматически.
- Оператор свободной функции симметричен относительно неявных преобразований компилятора
- Это самая эффективная реализация оператора? Вы можете найти в отношении копий
Оперативность оператора?
Когда вы звоните оператору? на двух элементах третий объект должен быть создан и возвращен. Используя описанный выше подход, копия выполняется в вызове метода. Как таковой, компилятор может исключить копию, когда вы передаете временный объект. Обратите внимание, что это следует читать как «компилятор знает , что он может удалить копию», а не как «компилятор будет удалять копию». Пробег зависит от разных компиляторов, и даже один и тот же компилятор может давать разные результаты при разных запусках компиляции (из-за разных параметров или ресурсов, доступных оптимизатору).
В следующем коде будет создан временный файл с суммой a
и b
, и этот временный код должен быть снова передан в operator+
вместе с c
для создания второго временного файла с конечным результатом. :
Q a, b, c;
// initialize values
Q d = a + b + c;
Если operator+
имеет семантику передачи по значению, компилятор может исключить копию передачи по значению (компилятор знает, что временный объект будет разрушен сразу после второго вызова operator+
, и ему не нужно создавать другая копия для передачи)
Даже если operator?
может быть реализован как однострочная функция (Q operator+( Q lhs, Q const & rhs ) { return lhs+=rhs; }
) в коде, это не должно быть так. Причина в том, что компилятор не может знать, является ли ссылка, возвращаемая operator?=
, на самом деле ссылкой на тот же объект или нет. Если оператор return явно принимает объект lhs
, компилятор знает, что возвращаемую копию можно удалить.
Симметрия относительно типов
Если существует неявное преобразование из типа T
в тип Q
, и у вас есть два экземпляра t
и q
соответственно каждого типа, то вы ожидаете, что (t+q)
и (q+t)
оба будут отозваны. Если вы реализуете operator+
как функцию-член внутри Q
, то компилятор не сможет преобразовать объект t
во временный объект Q
, а затем вызвать (Q(t)+q)
, поскольку он не может выполнять преобразования типов в левая сторона для вызова функции-члена. Таким образом, с реализацией функции-члена t+q
не будет компилироваться.
Обратите внимание, что это также верно для операторов, которые не являются симметричными в арифметических терминах, мы говорим о типах. Если вы можете вычесть T
из Q
, повысив T
до Q
, то нет никаких оснований не иметь возможности вычесть Q
из T
с помощью другого автоматического повышения.