Я вижу разницу в сгенерированном коде в зависимости от того, я ли я явно использую конструктор копирования или вручную пишу одну и ту же вещь. Это простой класс, который содержит только int и определяет на нем некоторые арифметические операторы.
Как clang, так и g++ обрабатывают эту ситуацию аналогичным образом, поэтому мне стало интересно, существует ли для этого основное требование языка, и если да, то что он делает? Ищите цитаты в стандарте, если это возможно. :)
Чтобы показать это в действии, я написал функцию average() двумя способами, работая на raw ints, а также на Holders. Я ожидал, что два будут генерировать один и тот же код. Вот результат:
Явный конструктор копии по умолчанию:
average(Holder, Holder):
add esi, edi
mov eax, esi
shr eax, 31
add eax, esi
sar eax
ret
average(int, int):
add esi, edi
mov eax, esi
shr eax, 31
add eax, esi
sar eax
ret
Это то же самое! Удивительно, правда? Возникает вопрос, когда я забываю "по умолчанию" реализацию и просто вручную пишу версию. До сих пор у меня создалось впечатление, что у этого должен быть тот же результирующий код, что и код по умолчанию, но это не так.
ручной конструктор копирования
average(Holder, Holder):
mov edx, DWORD PTR [rdx]
mov ecx, DWORD PTR [rsi]
mov rax, rdi
add ecx, edx
mov edx, ecx
shr edx, 31
add edx, ecx
sar edx
mov DWORD PTR [rdi], edx
ret
average(int, int):
add esi, edi
mov eax, esi
shr eax, 31
add eax, esi
sar eax
ret
Я пытаюсь понять причину этого, и соответствующие цитаты из стандарта наиболее ценятся.
Вот код
#define EXPLICITLY_DEFAULTED_COPY_CTOR true
class Holder {
public:
#if EXPLICITLY_DEFAULTED_COPY_CTOR
Holder(Holder const & other) = default;
#else
Holder(Holder const & other) noexcept : value{other.value} { }
#endif
constexpr explicit Holder(int value) noexcept : value{value} {}
Holder& operator+=(Holder rhs) { value += rhs.value; return *this; }
Holder& operator/=(Holder rhs) { value /= rhs.value; return *this; }
friend Holder operator+(Holder lhs, Holder rhs) { return lhs += rhs; }
friend Holder operator/(Holder lhs, Holder rhs) { return lhs /= rhs; }
private:
int value;
};
Holder average(Holder lhs, Holder rhs) {
return (lhs + rhs) / Holder{2};
}
int average(int lhs, int rhs) {
return (lhs + rhs) / int{2};
}
Если это ожидается, то есть ли что-нибудь, что я могу сделать для рукописной реализации, которая заставит его генерировать тот же код, что и версия по умолчанию? Я думал, что noexcept может помочь, но это не так.
Примечания. Если я добавлю конструктор перемещения, эта же проблема останется, но это не так, как вместо конструктора копирования. Это основная причина, по которой я ищу, а не только обходные пути. Я не заинтересован в просмотре кода или комментариях по стилю, которые не имеют прямого отношения к тому, чтобы отвечать на вопрос, почему генерация кода отличается, потому что это сильно сведено к минимуму, чтобы показать проблему, о которой я прошу.
Смотрите его в прямом эфире на Godbolt: https://godbolt.org/g/YA5Zsq