Оператор выделения C ++ с использованием деструктора и конструктора копирования - PullRequest
0 голосов
/ 31 марта 2020

Я работал над классом с несколькими динамическими c полями и искал быстрый способ кодирования оператора присваивания.

Итак, скажем, у меня есть некоторый базовый c класс Cla, который хранит динамический c массив целых чисел (arr) и размер указанного массива (n).

Я кодировал это:

Cla::Cla(int* arr, int n) : n(n)
{
    this->arr = new int[n];

    //allocation error handling

    while (--n >= 0)
        this->arr[n] = arr[n];
}


Cla::~Cla()
{
    delete[] arr;
}

Cla::Cla(const Cla& copy) : Cla(copy.arr, copy.n){}

Cla& Cla::operator= (const Cla& asg)
{
    this->~Cla();
    *this = asg;
    return *this;
}

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

Можете ли вы дать мне несколько советов о том, как исправить этот код? Возможно ли вообще так работать? (Я знаю, как написать оператор присваивания, я просто спрашиваю, возможно ли это сделать, используя деструктор и конструктор копирования. Я не смог найти ничего подобного в inte rnet.)

1 Ответ

1 голос
/ 31 марта 2020

Ваш operator= имеет неопределенное поведение. Во-первых, вы не можете вручную вызвать деструктор для объекта, который не был выделен с placement-new. Во-вторых, как только объект уничтожен, он больше не может использоваться, что означает, что *this = asg обращается к недопустимой памяти после вызова this->~Cla(), так как this больше не указывает на действительный объект. В-третьих, ваш operator= выполняет бесконечную рекурсию l oop, вызывая себя снова и снова, пока стек вызовов не взорвется (если вам повезет).

Поскольку вы хотите использовать свой конструктор копирования, ваш operator= лучше использовать вместо copy-swap idiom . Создайте локальный объект, чтобы использовать ваш конструктор копирования, а затем поменяйте содержимое этого объекта на this, чтобы this стал владельцем скопированных данных, а локальный объект освобождает старые данные, когда он выходит из области видимости, Например:

Cla& Cla::operator= (const Cla& asg)
{
    if (&asg != this)
    {
        Cla temp(asg);
        std::swap(arr, temp.arr);
        std::swap(n, temp.n);
    }
    return *this;
}

В качестве альтернативы:

void Cla::swap(Cla &other)
{
    std::swap(arr, other.arr);
    std::swap(n, other.n);
}

Cla& Cla::operator= (const Cla& asg)
{
    if (&asg != this) {
        Cla(asg).swap(*this);
    }
    return *this;
}

При этом факт, что ваш конструктор копирования делегирует вашему конструктору преобразования, означает, что вы используете C ++ 11 или более поздней версии, и в этом случае вы должны также реализовать семантику перемещения в своем классе, а не просто копировать семантику, например:

Cla::Cla() : arr(nullptr), n(0)
{
}

Cla::Cla(int* arr, int n) : arr(new int[n]), n(n)
{
    while (--n >= 0)
        this->arr[n] = arr[n];
}

Cla::Cla(Cla &&c) : arr(nullptr), n(0)
{
    c.swap(*this);
}

Cla::Cla(const Cla& c) : Cla(c.arr, c.n)
{
}

Cla::~Cla()
{
    delete[] arr;
}

void Cla::swap(Cla &other)
{
    std::swap(arr, other.arr);
    std::swap(n, other.n);
}

Cla& Cla::operator= (Cla asg)
{
    asg.swap(*this);
    return *this;
}

Передав параметр asg по значению, operator= может решить использовать ли семантику копирования или перемещать семантику на сайте вызова в зависимости от того, передается ли ему значение lvalue или rvalue. Компилятор выберет соответствующий конструктор для создания параметра asg, а затем this может стать владельцем полученных данных.

...