Копирование и перемещение концепции использования смарт-указатель


Я пытался реализовать простой класс-контейнер с помощью копирования и перемещения фразеологизм. Я учусь изменения в C++ как в C++11 и позже.

  1. Исключением является код безопасности?
  2. Я решил использовать смарт-указатель. Это std::unique_ptr безопасным выбором? Я читал, что это не должны быть скопированы, но так как я делаю копирование содержимого явно, и поскольку она обеспечивает функции swap, я не мог видеть каких-либо возражений. Есть ли предпочтительный практики, с учетом выбора типа контента может быть любой тип, включая тип объекта?
  3. Когда я позволить компилятору сделать копию для копирования оператор присваивания (как ...::operator=(MyContainer other)) У меня неопределенность с переездом оператор присваивания. Поэтому я решил поставить операции копирования в функцию (которую я предпочитаю визуально, это может выглядеть наивным и олдскул, но как старение мозга я нахожу его более четким). Также здесь есть еще один, предпочтительный практике? И преодолеть мои личные предпочтения: какова реальная разница позволять компилятору делать копию? Оба имеют место в стеке, не так ли?

Заголовок части

class MyContainer
{
private:
    std::size_t mSize;
    std::unique_ptr<int[]> mArray;

    static void _swap(MyContainer& first, MyContainer& second);
    void _reset(void);

public:
    MyContainer(std::size_t size); // default constructor
    MyContainer(const MyContainer& other); // copy constructor
    MyContainer(MyContainer&& other) noexcept; // move constructor
    MyContainer& operator=(const MyContainer& other); // copy assignment operator
    MyContainer& operator=(MyContainer&& other) noexcept; // move assignment operator
    void dump(void);
    /* ... */
    ~MyContainer(void);
};

Источник часть

void MyContainer::_swap(MyContainer& first, MyContainer& second) // static
{
    std::swap(first.mSize, second.mSize);
    first.mArray.swap(second.mArray);
}

void MyContainer::_reset(void)
{
    mSize = 0;
    mArray.reset();
}

MyContainer::MyContainer(std::size_t size = 0) // default constructor
    : mSize(size),
    mArray(mSize ? new int[mSize] : nullptr)
{
    // for testing purpose only
    for (int i = 0; i < mSize; i++)
        mArray.get()[i] = i;
}

MyContainer::MyContainer(const MyContainer& other) // copy constructor
    : mSize(other.mSize),
    mArray(mSize ? new int[mSize] : nullptr)
{
    std::copy(other.mArray.get(), other.mArray.get() + mSize, mArray.get());
}

MyContainer::MyContainer(MyContainer&& other) noexcept // move constructor
    : MyContainer() // init using default constructor (C++11)
{
    _swap(*this, other);
}

MyContainer& MyContainer::operator=(const MyContainer& other) // copy assignment
{
    MyContainer tmp(other);
    _swap(*this, tmp);
    return *this;
}

MyContainer& MyContainer::operator=(MyContainer&& other) noexcept // move assignment
{
    if (this != &other)
    {
        _reset();
        _swap(*this, other);
    }
    return *this;
}


void MyContainer::dump(void)
{
    std::cout << "Size: " << mSize << std::endl;
    for (int i = 0; i < mSize; i++)
        std::cout << mArray.get()[i] << std::endl;
}

MyContainer::~MyContainer(void)
{
}


428
3
задан 10 апреля 2018 в 10:04 Источник Поделиться
Комментарии
2 ответа

unique_ptr нормально здесь. Он используется там, где вы бы использовали обычный указатель перед.

Но не используйте голые new — использовать make_unique. Ох, и я думаю, что unique_ptr не типов массив в C++11, но нуждается в C++14.

Не использовать (пустота) Для () в списках параметров! Это было не правильным, даже в C++98. (это было поддержано, чтобы позволить совместимость с заголовками C, но по словам Бьярне это “мерзость”.

Не пишите пустой деструктор. Использовать по умолчанию, чтобы подчеркнуть, что она существует, или просто полностью оставить его.

Почему частный _swap статическую функцию, а не являющихся членами перегрузки swap что кто-то может использовать?

Еще одна важная новая функция у вас не приняли еще ряд for петли. Что г-жа Лагаси for петли у вас есть, которые идут через весь массив.

3
ответ дан 10 апреля 2018 в 12:04 Источник Поделиться

Ваши конкретные вопросы

Да, я бы сказал, что unique_ptr Это точно. Единственное место, где мы используем newнет окна для исключения утечки памяти. Деструктор очищает нас, и мы никогда не используем указанное в памяти после того, как он ушел.

Молодцы на надежную реализацию!

Рассмотрим следующий будет полировать, а не рвать свой код.

Отсутствующие заголовки

Заголовок файла должен

#include <cstddef>              // size_t
#include <memory> // unique_ptr

И реализации потребностей:

#include <algorithm>
#include <iostream>

Конструктор по умолчанию

Аргумент по умолчанию должен быть поставлен в заголовке, а не реализации. Это хорошая идея, чтобы сделать все один аргумент конструкторы (не копировать/переместить) explicitесли мы действительно хотим их преобразования конструкторов.

Если мы включаем <numeric>мы могли бы использовать std::iota() вместо петли, чтобы создать фиктивные данные (я не думаю что это обязательно лучше, но ты должен знать, что он существует). Что помогла если мы предоставляем begin() и end() методы:

public:
int *begin() { return mArray.get(); }
int *end() { return begin() + mSize; }
int const *begin() const { return mArray.get(); }
int const *end() const { return begin() + mSize; }

MyContainer::MyContainer(std::size_t size)
: mSize(size),
mArray(mSize ? new int[mSize] : nullptr)
{
// for testing purpose only
std::iota(begin(), end(), 0);
}

Конструктор копирования

Это выглядит хорошо, за исключением отсутствия проверки, чтобы избежать передачи нулевого указателя std::copy; он также может быть упрощена с помощью begin() и end() методы:

MyContainer::MyContainer(const MyContainer& other)
: mSize(other.mSize),
mArray(mSize ? new int[mSize] : nullptr)
{
if (mArray) {
std::copy(other.begin(), other.end(), begin());
}
}

Операторы присваивания

Они несколько легче реализовать, если мы пишем swap() в качестве члена, а не как метод класса:

MyContainer& MyContainer::swap(MyContainer& other)
{
using std::swap;
// Note: no need to check other != this, as self-swap is safe
swap(mSize, other.mSize);
swap(mArray, other.mArray);
return *this;
}

Это может быть публичный метод, как и стандартные контейнеры.

Затем задания просто

MyContainer& MyContainer::operator=(const MyContainer& other)
{
MyContainer tmp(other);
return swap(tmp);
}

MyContainer& MyContainer::operator=(MyContainer&& other) noexcept
{
return swap(other);
}

Тем не менее, мы можем объединить это в один метод, если мы передаем по значению - на безвозмездной основе в количестве выделений памяти:

MyContainer& MyContainer::operator=(MyContainer other) noexcept
{
return swap(other);
}

Хотя этот метод noexcept помните, что если компилятор должен скопировать в otherто, что копию может выдать - мы поддерживаем сильное исключение безопасности даже тогда.

Отметим также, что нет необходимости, чтобы сбросить до замены на новое значение - мы уходим other в допустимом состоянии, готов к его деструктор.

Деструктор

Не объявить пустой деструктор. Если вы создаете класс, который будет использоваться в качестве базового класса, напишите

virtual ~MyContainer() = default;

Но в этом случае лучше полностью исключить деструктор.

Метод Reset

Я не думаю, что это необходимо - мы можем просто присвоить пустую MyCollection.

Свалка способ

Предпочитаю пройти std::ostream использовать, а не только поддерживающих выход в std::cout.

Избежать std::endl если у вас есть реальная необходимость промывки поток.

Использовать беззнаковый тип для переменной цикла (или, использовать итераторы).


Измененный код

Это не имеет утечек (проверено в отчет). Я считаю, что это все-таки исключение-безопасный, хотя и не проверял, что конкретно.

// Декларации

#include <cstddef>
#include <iosfwd>
#include <memory>

class MyContainer
{
private:
std::size_t mSize;
std::unique_ptr<int[]> mArray;

public:
explicit MyContainer(std::size_t size = 0);
MyContainer(const MyContainer& other);
MyContainer(MyContainer&& other) noexcept;
MyContainer& operator=(MyContainer other) noexcept;
MyContainer& swap(MyContainer& other);
void dump(std::ostream& os);

int *begin() { return mArray.get(); }
int *end() { return begin() + mSize; }
int const *begin() const { return mArray.get(); }
int const *end() const { return begin() + mSize; }
};

// Реализации

#include <algorithm>
#include <ostream>
#include <numeric>

MyContainer::MyContainer(std::size_t size)
: mSize(size),
mArray(mSize ? new int[mSize] : nullptr)
{
// canned data for tests - remove for real use
if (mArray) {
std::iota(begin(), end(), 0);
}
}

MyContainer::MyContainer(const MyContainer& other)
: mSize(other.mSize),
mArray(mSize ? new int[mSize] : nullptr)
{
if (mArray) {
std::copy(other.begin(), other.end(), begin());
}
}

MyContainer::MyContainer(MyContainer&& other) noexcept
: mSize{0},
mArray{nullptr}
{
swap(other);
}

MyContainer& MyContainer::operator=(MyContainer other) noexcept
{
return swap(other);
}

MyContainer& MyContainer::swap(MyContainer& other)
{
// Note: no need to check other != this, as self-swap is safe
std::swap(mSize, other.mSize);
mArray.swap(other.mArray);
return *this;
}

void MyContainer::dump(std::ostream& os)
{
os << "Size: " << mSize << '\n';
for (auto it = begin(); it != end(); ++it)
os << ' ' << *it;
os << '\n';
}

// Тест

#include <iostream>
int main()
{
MyContainer a{6};
auto b = a;
a.dump(std::clog);
b.dump(std::clog);
std::clog << '\n';

MyContainer c;
c = std::move(a);
a.dump(std::clog);
c.dump(std::clog);
}


Предложенные расширения


  • Добавьте открытый size() и empty() методы - как только мы size()тогда dump() может быть общественные функции, не являющейся членом.

  • Добавить конструктор, который принимает std::initializer_list<int>и один, который принимает пару итераторов (бывшая может делегировать последнего; использовать std::distance() для получения необходимого размера из итераторов).

  • Сделать ее универсальной (т. е. шаблонный класс)

  • Добавить остальные begin и End методами (cbegin(), cend(), crbegin(), crend()и как const и не const реализации rbegin() и rend()). Объявить обычные имена для итератора типа: iterator, const_iterator, reverse_iterator, const_reverse_iterator.

3
ответ дан 10 апреля 2018 в 12:04 Источник Поделиться