Почему конструктор перемещения не используется при объявлении

В следующем коде конструктор перемещения производного класса, очевидно, не генерируется, хотя базовый класс является конструируемым перемещением.

#include <cstddef>
#include <memory>
#include <cstring>
#include <cassert>

template <typename T>
class unique_array : public std::unique_ptr<T[],void (*)(void*)>
{   size_t Size;
 protected:
    typedef std::unique_ptr<T[],void (*)(void*)> base;
    unique_array(T* ptr, size_t size, void (*deleter)(void*)) noexcept : base(ptr, deleter), Size(size) {}
 public:
    constexpr unique_array() noexcept : base(NULL, operator delete[]), Size(0) {}
    explicit unique_array(size_t size) : base(new T[size], operator delete[]), Size(size) {}
    unique_array(unique_array<T>&& r) : base(move(r)), Size(r.Size) { r.Size = 0; }
    void reset(size_t size = 0) { base::reset(size ? new T[size] : NULL); Size = size; }
    void swap(unique_array<T>&& other) noexcept { base::swap(other); std::swap(Size, other.Size); }
    size_t size() const noexcept { return Size; }
    T* begin() const noexcept { return base::get(); }
    T* end() const noexcept { return begin() + Size; }
    T& operator[](size_t i) const { assert(i < Size); return base::operator[](i); }
    unique_array<T> slice(size_t start, size_t count) const noexcept
    {   assert(start + count <= Size); return unique_array<T>(begin() + start, count, [](void*){}); }
};

template <typename T>
class unique_num_array : public unique_array<T>
{   static_assert(std::is_arithmetic<T>::value, "T must be arithmetic");
 public:
    using unique_array<T>::unique_array;
    unique_num_array(unique_num_array<T>&& r) : unique_array<T>(move(r)) {}
    unique_num_array<T> slice(size_t start, size_t count) const noexcept
    {   assert(start + count <= this->size()); return unique_num_array<T>(this->begin() + start, count, [](void*){}); }
 public: // math operations
    void clear() const { std::memset(this->begin(), 0, this->size() * sizeof(T)); }
    const unique_num_array<T>& operator =(const unique_num_array<T>& r) const { assert(this->size() == r.size()); memcpy(this->begin(), r.begin(), this->size() * sizeof(T)); return *this; }
    const unique_num_array<T>& operator +=(const unique_num_array<T>& r) const;
    // ...
};

int main()
{   // works
    unique_array<int> array1(7);
    unique_array<int> part1 = array1.slice(1,3);
    // does not work
    unique_num_array<int> array2(7);
    unique_num_array<int> part2 = array2.slice(1,3);
    // test for default constructor
    unique_num_array<int> array3;
    return 0;
}

С помощью приведенного выше кода я получаю сообщение об ошибке (gcc 4.8.4):

test6.cpp: В функции 'int main()': test6.cpp:47:48: ошибка: использование удаленной функции 'unique_num_array::unique_num_array(const unique_num_array&)' unique_num_array part2 = array2.slice(1,3);

Функция слайса в производном классе не может возвращаться по значению, поскольку отсутствует конструктор перемещения. Все остальные конструкторы работают должным образом (не рассматривается в этом примере).

Если я определю конструктор перемещения в явном виде (раскомментируйте строку), пример скомпилируется. Но в этом случае конструктор по умолчанию исчезает, что, конечно, не предназначено.

Что здесь происходит? Я не понимаю ни одного из случаев.

Почему конструктор перемещения удаляется в первом случае?

Почему конструктор по умолчанию отбрасывается во втором случае? Другие, кажется, выживают.

2 ответа

Решение

Здесь применяются два набора правил:

  1. Ни конструктор перемещения, ни конструктор по умолчанию не охватываются директивой using.

    [...] Все кандидаты, унаследованные конструкторы , которые не являются конструктором по умолчанию или конструктором копирования / перемещения и подписи которых не соответствуют пользовательским конструкторам в производном классе, неявно объявляются в производном классе.

  2. Теперь применяются правила автоматической генерации неявных конструкторов (как уже упоминалось в xskxsr).

    Если определение класса X явно не объявляет конструктор перемещения, неявный неявно будет объявлен как дефолтный, если и только если [...] X не имеет объявленного пользователем оператора копирования.

    [...] Если для класса X нет объявленного пользователем конструктора, неявный конструктор без параметров неявно объявляется как дефолтный ([dcl.fct.def]).

Почему конструктор перемещения удаляется в первом случае?

Потому что есть объявленный пользователем оператор копирования в unique_num_array<T>, конструктор перемещения не объявляется неявно компилятором. Стандарт в [class.copy.ctor]/8 гласит:

Если определение класса X явно не объявляет конструктор перемещения, неявный неявно будет объявлен как дефолтный, если и только если

  • X не имеет объявленного пользователем конструктора копирования,

  • X не имеет заявленного пользователем оператора копирования,

  • X не имеет объявленного пользователем оператора назначения перемещения, и

  • X не имеет объявленного пользователем деструктора.


Почему конструктор по умолчанию отбрасывается во втором случае?

Потому что есть объявленный пользователем конструктор перемещения в unique_num_array<T>, конструктор по умолчанию не объявляется неявно компилятором. Стандарт в [class.ctor]/4 говорит

... Если для класса X нет объявленного пользователем конструктора, неявный конструктор без параметров неявно объявляется как дефолтный ([dcl.fct.def]).


Кроме того, этот код будет работать после C++17 из-за гарантированного разрешения копирования. Подробно, до C++17, семантика обоих контекстов

return unique_num_array<T>(...);

а также

unique_num_array<int> part2 = array2.slice(1,3);

требует операции копирования / перемещения, в то время как после C++17 семантика становится такой, что целевой объект инициализируется инициализатором prvalue без материализации временного объекта, поэтому копирование / перемещение не требуется.

Другие вопросы по тегам