Почему нельзя использовать функцию, не являющуюся членом, для перегрузки оператора присваивания?
Оператор присваивания может быть перегружен с использованием функции-члена, но не не-члена friend
функция:
class Test
{
int a;
public:
Test(int x)
:a(x)
{}
friend Test& operator=(Test &obj1, Test &obj2);
};
Test& operator=(Test &obj1, Test &obj2)//Not implemented fully. just for test.
{
return obj1;
}
Это вызывает эту ошибку:
ошибка C2801: оператор = должен быть нестатическим членом
Почему не может friend
функция используется для перегрузки оператора присваивания? Компилятор позволяет перегружать другие операторы, такие как +=
а также -=
с помощью friend
, Какова внутренняя проблема / ограничение в поддержке operator=
?
9 ответов
Потому что по умолчанию operator=
предоставляемый компилятором (для каждого элемента копия один) всегда будет иметь приоритет. Т.е. твой друг operator=
никогда бы не позвонил.
РЕДАКТИРОВАТЬ: Этот ответ отвечает на
В чем заключается проблема / ограничение в поддержке оператора =?
часть вопроса. Другие ответы здесь указывают на ту часть стандарта, в которой говорится, что вы не можете этого сделать, но, скорее всего, именно поэтому эта часть стандарта была написана именно так.
Во-первых, следует отметить, что это не имеет ничего общего с оператором, который реализуется как друг. На самом деле речь идет о реализации копирования-назначения как функции-члена или как не-членской (автономной) функции. Будет ли эта автономная функция другом или нет, совершенно не имеет значения: это может быть, а может и не быть, в зависимости от того, к чему она хочет получить доступ в классе.
Теперь ответ на этот вопрос дан в книге D&E ( Дизайн и эволюция C++). Причина этого в том, что компилятор всегда объявляет / определяет оператор копирования-присваивания члена для класса (если вы не объявляете свой собственный оператор копирования-присваивания члена).
Если язык также позволял объявлять оператор копирования-копирования как отдельную (не являющуюся членом) функцию, вы можете получить следующее
// Class definition
class SomeClass {
// No copy-assignment operator declared here
// so the compiler declares its own implicitly
...
};
SomeClass a, b;
void foo() {
a = b;
// The code here will use the compiler-declared copy-assignment for `SomeClass`
// because it doesn't know anything about any other copy-assignment operators
}
// Your standalone assignment operator
SomeClass& operator =(SomeClass& lhs, const SomeClass& rhs);
void bar() {
a = b;
// The code here will use your standalone copy-assigment for `SomeClass`
// and not the compiler-declared one
}
Как видно из приведенного выше примера, семантика копирования-присваивания будет меняться в середине блока перевода - перед объявлением вашего автономного оператора используется версия компилятора. После объявления ваша версия используется. Поведение программы будет меняться в зависимости от того, где вы разместили объявление вашего автономного оператора копирования-назначения.
Это считалось неприемлемо опасным (и это так), поэтому C++ не позволяет объявлять оператор присваивания копии как отдельную функцию.
Это правда, что в вашем конкретном примере, в котором, в частности, используется функция друга, оператор объявляется очень рано, внутри определения класса (поскольку именно так объявляются друзья). Итак, в вашем случае компилятор, конечно же, сразу узнает о существовании вашего оператора. Однако с точки зрения языка C++ общая проблема никак не связана с дружественными функциями. С точки зрения языка C++ речь идет о функциях-членах, а не о функциях-членах, а перегрузка копирования-присваивания, не связанная с членами, просто полностью запрещена по причинам, описанным выше.
$13.5.3 - "Оператор присваивания должен быть реализован нестатической функцией-членом с ровно одним параметром. Поскольку оператор оператора копирования = неявно объявляется для класса, если он не объявлен пользователем (12.8), присвоение базового класса оператор всегда скрыт оператором копирования копии производного класса."
Потому что есть некоторые операторы, которые ДОЛЖНЫ быть членами. Эти операторы:operator[]
operator=
operator()
operator->
и операторы преобразования типов, такие как operator int
,
Хотя можно объяснить, почему именно оператор = должен быть членом, их аргумент не может применяться к другим в списке, что заставляет меня поверить, что ответ "почему" - "просто потому что".
НТН
operator=
это специальная функция-член, которую компилятор предоставит, если вы не объявите ее самостоятельно. Из-за этого особого статуса operator=
имеет смысл, что ro требует, чтобы она была функцией-членом, поэтому нет никакой возможности быть одновременно сгенерированным компилятором членом operator=
и объявленный пользователем друг operator=
и нет возможности выбора между ними.
Почему функция друга не может быть использована для перегрузки оператора присваивания?
Краткий ответ: просто потому что.
Несколько более длинный ответ: так был исправлен синтаксис. Несколько операторов должны быть функциями-членами. Оператор присваивания является одним из
Намерение operator=
является операцией присваивания текущему объекту Тогда LHS, или lvalue, является объектом того же типа.
Рассмотрим случай, когда LHS представляет собой целое число или некоторый другой тип. Это дело рассматривается operator int()
или соответствующий operator T()
функция. Следовательно, тип LHS уже определен, но не является членом operator=
функция может нарушить это.
Следовательно, этого избегают.
Потому что в классе уже есть неявная функция перегрузки операторов для '=', которая выполняет поверхностное копирование. Таким образом, даже если вы перегрузите с помощью функции друга, вы никогда не сможете вызвать ее, так как любой вызов, сделанный нами, вызовет неявный метод поверхностного копирования, а не перегруженную функцию друга.
Этот пост относится к C++11
Почему кто-то хочет не-член operator=
? Ну, с членом operator=
тогда возможен следующий код:
Test const &ref = ( Test() = something );
который создает висячую ссылку. Оператор, не являющийся членом, исправит это:
Test& operator=(Test &obj1, Test obj2)
потому что теперь prvalue Test()
не сможет связать с obj1
, Фактически, эта подпись обеспечила бы, чтобы мы никогда не возвращали висячую ссылку (если, конечно, нам не была предоставлена одна) - функция всегда возвращает "правильное" lvalue, потому что она принудительно вызывает вызов с lvalue.
Однако в C++11 теперь есть способ указать, что функция-член может вызываться только для lvalues, поэтому вы можете достичь той же цели, написав функцию-член:
Test &operator=(Test obj2) &
// ^^^
Теперь приведенный выше код со свисающей ссылкой не удастся скомпилировать.
NB. operator=
должен занимать правую часть либо по значению, либо по константной ссылке. Взятие по значению полезно при реализации идиомы копирования и обмена, техники, позволяющей легко писать безопасные (но не обязательно самые быстрые) операторы копирования-назначения и перемещения-назначения.