#cpp #перегрузка_операторов
Не могу разобраться, как перегрузить оператор присваивания. С бинарными операторами более-менее всё понятно, там хотя-бы два операнда, а вот с этим - никак. Не могли бы вы привести пример перегрузки "=", и разъяснить что где делает, и результат перегрузки? Например перегрузить так, чтобы он к присваиваемому числу прибавлял + 5, или что-то вроде, и показать, к чему и от чего присваивается и к чему где прибавляется.
Ответы
Ответ 1
Vector& Vector::operator=(Vector& v)//перегрузка { x=v.x;y=v.y;z=v.z; return *this;//возвращаем ссылку на текущий объект } Не знаю, что вызвало сомнения и вопросы. Главное после присваивания вернуть ссылку на текущий объект.Ответ 2
Модный вариант в духе C++11 с использованием семантик обмена и переноса: class C { public: C(std::string someName) : name(someName) {} void swap(C& other) { name.swap(other.name); } C(const C& other) : name(other.name) // конструктор копирования из lvalue { } C(C&& other) // конструктор копирования из rvalue, он же конструктор переноса { this->swap(other); } C& operator=(C other) // оператор присваивания { // передача параметра по значению важна! this->swap(other); // обмен с временной копией return *this; } private: std::string name; }; (закончились комментарии, переношу сюда) @avp: IMHO чем дальше, тем С++ становится непонятней. Видимо из языка для практического программирования он вскоре превратится в язык для определенной касты хакеров, делающих инструменты для изготовления инструментов. Вопрос тут ведь в том, какое количество обычных программистов смогут хотя бы использовать (я уж не говорю о модификации) созданные ими конечные инструменты. @VladD: C++ сложнеет, это факт. Кроме этого, появились ещё нативные языку лямбды и куча всего. Из моей практики, семантика shallow-копирования объектов (kak eto po-russki?), которая генерируется по умолчанию для объектов C++, почти никогда не нужна. IMHO гораздо лучше была бы семантика noncopyable по умолчанию. @avp: @VladD, после замены C(C&& other) на C(C& other) получилось avp@avp-xub11:~/src/dispro$ g++ -std=c++0x t.cpp avp@avp-xub11:~/src/dispro$ ./a.out swap, other = (0xbfce8970) c1 -> c2 -> c1 avp@avp-xub11:~/src/dispro$ Так и было задумано? Что-то я вообще смысла здесь не вижу. Кстати, а вопрос-то был об операторе =. @VladD: @avp: Нет, задумано было по-другому. Должно быть C(const C& other) : name(other.name) // конструктор копирования из lvalue { } (у меня так в ответе) Должно работать правильно. А по поводу оператора присваивания, в коде выполняется сначала копирование, other в операторе является уже копией. А значит, у неё можно "украсть" значение и оставить её умирать в конце функции. Идея в том, что копирования всё равно не избежать, а перенос является дешёвой операцией. @avp: @VladD, это я понял. Поправил конструктор, как в ответе и заработало. Интересно, swap используется для эффективности? Просто происходит обмен указателями? Все это так глубоко зарыто, что хочется просто махнуть на эти новшества рукой и не брать на работу тех, кто так пишет. @VladD: @avp: идея в том, что swap пишется один раз, а остальные функции/операторы его по возможности используют. Тем самым уменьшается повтор кода. Плюс swap не должен давать исключений, ЕМНИП, с вытекающей оттуда exception-safety. @avp: @VladD, насчет swap пишется один раз тоже ясно. Не совсем ясен его смысл. Смотрите, в конструктор строка передается по значению? Если так, то байты все равно копируются. Потом проводится обмен указателями с временной копией и очевидно когда-нибудь у временной копии будет вызван деструтор. Так? Чем это лучше передачи в консруктор ссылки и копирования байт в нем? Или я чего-то фундаментального не понимаю? @VladD: @avp: ничем не лучше, просто меньше думать: все операции стандартным образом выражаются через swap. Смотрите: старые данные надо деаллоцировать. Мы доверяем это деструктору временной копии, а не пишем сами руками. Точно так же в операторе присваивания создание копии мы доверяем конструктору копирования, а не пишем сами. @avp: @VladD, зачем при передаче по ссылке что-то деаллоцировать? IMHO при передаче по значению просто добавляются лишние вызовы конструктора и деструктора временной копии. Где-то я ошибаюсь? Какой-то странный подход. @avp: наверное, я не очень хорошо описал. Смотрите: у объекта o1 вызван оператор присваивания с аргументом o2. При этом он должен деаллоцировать свои старые данные, и на их место скопировать данные из o2. Если объявлена передача по значению, то к нам приходит уже временная копия o3. Все возможные исключения брошены в конструкторе копирования. Чтобы самим не писать ещё раз деаллокацию старых данных и перемещение новых из o3, мы пользуемся swap и деструктором o3. Написать традиционным образом было бы немного эффективнее (но современные компиляторы должны соптимизировать), но был бы дубляж кода с конструктором переноса. (Конструктор переноса нужен, чтобы эффективно выполнялся код наподобие C c(f());, без ненужного копирования.)Ответ 3
Много лет назад нашёл такой универсальный вариант: при присваивании вызвать деструктор старого объекта и инициализировать это место новым объектом. #includeclass T { int x; public: T(int _x=0): x(_x) { std::cerr<<"T(int "< ~T(); new (this) T(t); } return *this; } friend class C; }; class C { T x; public: C(T _x=0): x(_x) { std::cerr<<"C(T "< Ответ 4
Минимальный оператор присваивания - это void Cls::operator=(Cls other) { swap(*this, other); } Согласно стандарту, это копирующий оператор присваивания. Однако он также может выполнять перемещение, если у Cls есть перемещающий конструктор: Cls a, b; a = std::move(b); // Работает как // Cls other(std::move(b)); a.operator=(other); // ^^^^^^^^^^ // перемещение: вызов Cls::Cls(Cls&&) После обмена (swap) текущие члены класса оказываются во временном объекте other и удаляются при выходе из оператора присваивания. При копирующем присваивании самому себе будет сделана лишняя копия, но никаких ошибок не будет. Тип результата может быть любым. Автоматически сгенерированный оператор присваивания имеет тип возвращаемого значения Cls& и возвращает *this. Это позволяет писать код вида a = b = c или (a = b) > c. Но многие соглашения по стилю кода такое не одобряют, в частности см. CppCoreGuidelines ES.expr "Avoid complicated expressions". Для работы этого оператора присваивания нужны конструкторы копирования/перемещения и функция обмена (swap). Вместе это выглядит так: class Cls { public: Cls() {} // Конструктор копирования Cls(const Cls& other) : x(other.x), y(other.y) {} // Конструктор перемещения Cls(Cls&& other) noexcept { swap(*this, other); } // Оператор присваивания void operator=(Cls other) noexcept { swap(*this, other); } // Обмен friend void swap(Cls& a, Cls& b) noexcept { using std::swap; // Добавление стандартной функции в список перегрузок... swap(a.x, b.x); // ... и вызов с использованием поиска по типам аргументов (ADL). swap(a.y, b.y); } private: X x; Y y; }; Конструктор копирования копирует каждый член класса. Конструктор перемещения конструирует пустые члены класса, и затем обменивает их со своим аргуменом. Можно перемещать каждый член по отдельности, но удобнее использовать swap. Функция swap может быть свободной функцией-другом. Многие алгоритмы ожидают наличие свободной функции swap, и вызывают ее через поиск по типу аргументов (ADL). Раньше рекомендовалось также писать метод swap, чтобы можно было писать f().swap(x);, но с появлением семантики перемещения это стало не нужно. Если функции не могут бросать исключений, то они должны быть помечены как noexcept. Это нужно для std::move_if_noexcept и других функций, которые могут использовать более эффективный код, если присваивание или конструктор не бросает исключений. Для такого классавыдают std::is_nothrow_copy_constructible == 0 std::is_nothrow_move_constructible == 1 std::is_nothrow_copy_assignable == 0 std::is_nothrow_move_assignable == 1 Хотя оператор присваивания и помечен как noexcept, при его вызове с аргументом const Cls& произойдет копирование, которое может бросить исключение. По этому is_nothrow_copy_assignable возвращает false.
Комментариев нет:
Отправить комментарий