Перегрузка оператора присваивания и копирования (C++)

Модератор: Модераторы разделов

BratSinot
Сообщения: 812
ОС: Slackware64

Перегрузка оператора присваивания и копирования

Сообщение BratSinot »

Доброго времени суток!

У меня есть класс, в нем есть деструктор который освобождает память. В классе перегружены некоторые операторы, но в них формируется объект и возвращается. Соответственно вызывается деструктор:

Код: Выделить всё

Binary Binary::operator+ (Binary &b) {
<...>
        Binary c;
<...>
        return c;
}


Я перегрузил операторы присваивания и копирования:

Код: Выделить всё

class Binary {
<...>
        Binary(const Binary &a);
        Binary operator= (const Binary &a);
<...>
};

Binary Binary::operator= (const Binary &a) {
    n = a.n;
    bin_len = a.bin_len;
    bin = new char[bin_len];
    memcpy(bin, a.bin, bin_len);

    return *this;
}
Binary::Binary(const Binary &a) {
    n = a.n;
    bin_len = a.bin_len;
    bin = new char[bin_len];
    memcpy(bin, a.bin, bin_len);
}


Оно вроде работает, но если я не буду перегружать оператор копирования, то ничего не будет работать. Что вообще происходит? Внятных примеров я так и не нашел, что в книгах, что в интернете.
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: Перегрузка оператора присваивания и копирования

Сообщение NickLion »

1. Нет оператора копирования. Есть констутор копирования.
2. Binary a = c; === Binary a(с ); (констуктор копирования вызывается)
а совсем не
Binary a; a = c; (а не констуктор, а потом оператор присваивания).
Спасибо сказали:
BratSinot
Сообщения: 812
ОС: Slackware64

Re: Перегрузка оператора присваивания и копирования

Сообщение BratSinot »

А что происходит при:

Код: Выделить всё

    Binary a;

    return a;
Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: Перегрузка оператора присваивания и копирования

Сообщение drBatty »

BratSinot писал(а):
21.09.2013 20:35
Binary operator= (const Binary &a);

1. согласен с NickLion -- есть оператор копирования(присваивания), который "равно". operator=(). А есть конструктор копий, он вызывается при создании нового объекта на основе старого.
Вот код:

Код: Выделить всё

int main()
{
  Foo x; // просто создание объекта, конструктор без параметров
  Foo y(y); // конструктор копирования
  Foo z = y; // тоже конструктор копирования, НЕ оператор
  z = x; // а вот это уже оператор Foo::operator=()
}

2. вообще-то оператор= перезагружается так:

Код: Выделить всё

class Foo{
...
const Foo &operator=(const Foo &)
{
...
return *this;
}
};

т.е. возвращает он константную ссылку на скопированный объект. Это нужно для правильной работы выражений типа x=y=z;
BratSinot писал(а):
21.09.2013 20:35
Что вообще происходит?

по отрывкам кода не очень понятно, похоже вы слишком много копий делаете. Ведь если вы возвращаете объект, то компилятор создаёт временную копию Binary, что-бы взять от неё ссылку годную для operator=() и для конструктора копий.

Такое можно и нужно(приходится) делать в случае operator+(const Foo &x, const Foo &y), ибо эта дружественная функция по своей сути плодит лишнюю сущность (сумму). Тоже самое будет и с Foo::operator+(const Foo &y) const, ибо в этом случае сумма не запихивается в первое слагаемое, а создаётся. Лучше использовать operator+=(), что-бы не плодить лишние сущности. Тогда можно работать только со ссылками.
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: Перегрузка оператора присваивания и копирования

Сообщение NickLion »

BratSinot писал(а):
22.09.2013 12:49
А что происходит при:

Код: Выделить всё

    Binary a;

    return a;

В зависимости от реализации и настроек оптимизации:
1. копируется (конструктор копирования) "a" во временную перменную и уничтожается.
2. "a" и есть временная переменная.
Более того, если есть запись:
Binary x = y + z;
Может быть вызвано 2 деструктора (переменной "a" и временной), один (только временной, которая одновременно и "a") или вообще ни одного (x==temp==a).
Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: Перегрузка оператора присваивания и копирования

Сообщение drBatty »

BratSinot писал(а):
22.09.2013 12:49
Binary a;

создаётся временный объект a внутри метода.
BratSinot писал(а):
22.09.2013 12:49
return a;

а это великое колдунство: компилятор не может отдать a потому-что оно уничтожается (ибо a это временный объект). По этой причине компилятор создаёт безымянный временный объект -- копию a. И вот его-то и возвращает. Если там стоит какой-нить operator=(), который ждёт ссылку, то берётся ссылка от безымянного объекта, работает оператор=, а ПОСЛЕ его завершения временный безымянный объект уничтожается. Таким образом у программиста создаётся иллюзия, что внутренние объекты можно возвращать наружу.

NickLion писал(а):
22.09.2013 12:56
В зависимости от реализации и настроек оптимизации:

ЩИТО?
при чём тут реализация? Ну только если вы совсем белены объелись, и записали

Код: Выделить всё

#define Binary int

что-бы всех нае^Wобмануть. (:

NickLion писал(а):
22.09.2013 12:56
Может быть вызвано 2 деструктора (переменной "a" и временной), один (только временной, которая одновременно и "a") или вообще ни одного (x==temp==a).

похоже понял: это вы про случай, когда НЕ перезагрузили operator=(), и он у вас дефолтный, т.е. побитно копирует. При этом оптимизатор может вырезать побитное копирования самой переменной в саму себя.

Однако ТС явно написал, что он хочет перезагружать оператор=()
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: Перегрузка оператора присваивания и копирования

Сообщение NickLion »

drBatty писал(а):
22.09.2013 13:03
NickLion писал(а):
22.09.2013 12:56
В зависимости от реализации и настроек оптимизации:

ЩИТО?
при чём тут реализация? Ну только если вы совсем белены объелись, и записали

Код: Выделить всё

#define Binary int

что-бы всех нае^Wобмануть. (:

Никого не стараюсь обмануть:

Код: Выделить всё

#include <stdio.h>
#include <memory.h>

class A {
public:
    A()
    {
        printf("A::A()\n");
        memset(xx, 0, sizeof(xx));
    }

    A(const A& x)
    {
        printf("A::A(A)\n");
        memcpy(xx, x.xx, sizeof(xx));
        xx[0]++;
    }

    A& operator=(const A& x)
    {
        memcpy(xx, x.xx, sizeof(xx));
        xx[0]++;
    }

    ~A()
    {
        printf("A::~A()\n");
    }

private:
    int xx[256];
};


A f()
{
    A a;
    return a;
}

int main()
{
    A x = f();
    return 0;
}


g++ b.cpp -o b && ./b

A::A() A::~A()

Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: Перегрузка оператора присваивания и копирования

Сообщение drBatty »

NickLion писал(а):
22.09.2013 13:09
Никого не стараюсь обмануть:

ну тут компилятор смог заинлайнить конструкторы-деструкторы, и от них за счёт этого избавится. Слава роботам.

В хелловорлдах работает. В продакшене... Даже после незначительного усложнения вылазит эта лишняя сущность:

Код: Выделить всё

#include <stdio.h>
#include <memory.h>

class A {
public:
    A()
    {
        printf("A::A() this=%p\n", this);
        memset(xx, 0, sizeof(xx));
    }

    A(const A& x)
    {
        printf("A::A(A) this=%p\n", this);
        memcpy(xx, x.xx, sizeof(xx));
        xx[0]++;
    }

    const A &operator=(const A& x)
    {
        printf("operator() this=%p\n", this);
        memcpy(xx, x.xx, sizeof(xx));
        xx[0]++;
        return *this;
    }

    ~A()
    {
        printf("A::~A() this=%p\n", this);
    }

    int output_xx() const
    {
        return printf("xx[0]=%d\n", xx[0]);
    }

private:
    int xx[256];
};


A f()
{
    A a;
    return a;
}

int main()
{
    A x;
       x = f();
    x.output_xx();
    return 0;
}

Shell

$ g++ -O3 -Wall b.cpp && ./a.out A::A() this=0xbfecdcf0 A::A() this=0xbfece0f0 operator() this=0xbfecdcf0 A::~A() this=0xbfece0f0 xx[0]=1 A::~A() this=0xbfecdcf0


вот тут я действительно использую оператор=(), а не как вы -- просто такой значок. Ну и всё получается как я говорил выше.
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: Перегрузка оператора присваивания и копирования

Сообщение NickLion »

drBatty писал(а):
22.09.2013 13:44

Shell

$ g++ -O3 -Wall b.cpp && ./a.out A::A() this=0xbfecdcf0 A::A() this=0xbfece0f0 operator() this=0xbfecdcf0 A::~A() this=0xbfece0f0 xx[0]=1 A::~A() this=0xbfecdcf0


вот тут я действительно использую оператор=(), а не как вы -- просто такой значок. Ну и всё получается как я говорил выше.

Нет, получается то, что я говорил. Вместо 2-х конструкторов при вызове функции вызван 1. Первый — это переменная x, его не убрать. А второй — внутренняя переменная "a", она же временная переменная. Если не заметили, то конструктор копирования не был вызван ни разу.
Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: Перегрузка оператора присваивания и копирования

Сообщение drBatty »

NickLion писал(а):
22.09.2013 13:50
А второй — внутренняя переменная "a", она же временная переменная. Если не заметили, то конструктор копирования не был вызван ни разу.

ну дык а зачем создавать тут временный объект, если мы с ним всё равно ничего не делаем? Вот он и НЕ создаётся, используется в качестве копии сам объект. Вот только вы зря надеетесь на то, что также будет в реальной жизни.
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: Перегрузка оператора присваивания и копирования

Сообщение NickLion »

Да в том то и дело, что вижу это в реальной жизни.
Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: Перегрузка оператора присваивания и копирования

Сообщение drBatty »

NickLion писал(а):
22.09.2013 15:44
Да в том то и дело, что вижу это в реальной жизни.

слова "не нужно надеяться" вам непонятны? В простых случаях компилятор может выкинуть контекст, сквозь который он передаёт результат. В нашем случае и контекста никакого нет, только сам объект, очевидно, делать копию нет смысла. Но IRL контекст обязательно появится.
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: Перегрузка оператора присваивания и копирования

Сообщение NickLion »

Ну, так я в самом начале и говорил, что может получиться так, а может эдак. Я не надеюсь ни на что, что явно не оговорено в стандарте. Точнее не всегда и на это надеяться можно, но это уже считается ошибкой компилятора.
Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: Перегрузка оператора присваивания и копирования

Сообщение drBatty »

ИМХО лучше вообще не надеяться, что компилятор что-то там оптимизирует, и поймёт правильно. В данном случае надо учитывать вероятность того, что _возможно_ произойдёт вызов лишних конструкторов.
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали: