Код неожиданно не компилируется. Почему?

Взгляните на следующий пример кода, в котором используется класс uncopiable, аналогичный boost::noncopyable:

#include <vector>

class uncopiable {
    using self = uncopiable;
protected:
    uncopiable() {}
    ~uncopiable() {}
    uncopiable(const self&) = delete;
    self& operator=(const self&) = delete;
};

struct A {
    struct B : uncopiable {
        using self = B;

        B() {
        }

        B(B&&) = default;
        self& operator=(B&&) = default;

        ~B() {
        }
    };

    A() { v.emplace_back(); }
    ~A() {}

private:
    std::vector<B> v;
};

int main () {}

Поскольку я хотел, чтобы внутренний класс перемещался только, я явно указал его конструктор перемещения и оператор присваивания по умолчанию, но также, поскольку я слышал, что рекомендуется указывать все «специальные функции-члены», в таком случае я унаследовал его от uncopiable. Проблема в том, что компиляция завершается неудачей с каждым компилятором, и отображается что-то похожее на следующее сообщение об ошибке (это сообщение является выдержкой из сообщения clang):

/usr/include/c++/v1/memory:1645:31: ошибка: вызов неявно удаленного конструктора копирования 'A::B'

...

main.cpp: 26:10: примечание: здесь запрашивается специализация шаблона функции 'std::__1::vector >::emplace_back‹>'

main.cpp: 19: 3: примечание: конструктор копирования неявно удален, поскольку «B» имеет объявленный пользователем конструктор перемещения

Это можно было бы исправить, удалив наследование (операции копирования все равно не создавались бы). Но писать операции копирования, которые после этого явно удаляются внутри класса, тоже нормально.

Мои вопросы: почему это происходит? Можно ли считать недостатком отключение конструкторов/операторов присваивания через наследование вспомогательных классов?


person Predelnik    schedule 11.10.2015    source источник
comment
Ваш класс noncopiable также неявно является nonmovable, поэтому B также является неподвижным. = default явно запрашивает неявное определение, которое в этом случае удаляется. (По сути, установка специальных функций-членов по умолчанию — это просто способ изменить их объявление, а не их определение.)   -  person dyp    schedule 11.10.2015
comment
@dyp Хорошо, я уже давно использую такой класс, но поскольку предыдущая версия MSVC не позволяла указывать операции перемещения как default, все мои варианты использования работали нормально.   -  person Predelnik    schedule 11.10.2015


Ответы (2)


Проблема в том, что ваш класс uncopiable нельзя перемещать. Поэтому конструктор перемещения default/оператор присваивания производного класса пытается использовать версии копии deleted.

static_assert(std::is_move_constructible<uncopiable>::value, "");  // fails
static_assert(std::is_move_assignable<uncopiable>::value, "");     // fails

Причиной этого является § 12.8 ¶ 9:

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

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

Объявление оператора копирования или оператора присваивания как deleted по-прежнему считается его объявлением.

Решение, конечно, состоит в том, чтобы объявить операции перемещения для uncopiable.

uncopiable(uncopiable&&) noexcept = default;
uncopiable& operator=(uncopiable&&) noexcept = default;

Обратите внимание, что операции перемещения обычно должны быть объявлены noexcept. Особенно, если вы хотите использовать тип в std::vector, как в вашем примере.

person 5gon12eder    schedule 11.10.2015
comment
Я не думаю, что noexcept необходим, эти явно заданные по умолчанию конструкторы должны неявно быть noexcept: melpon.org/wandbox /permlink/mGzep1B9eLmOsiFE Конечно, добавить его не помешает. - person dyp; 11.10.2015
comment
@dyp Я этого не знал. - person 5gon12eder; 11.10.2015

Это нормально компилируется на MinGw:

#include <vector>

class uncopiable {
    using self = uncopiable;
protected:
    uncopiable() {}
    ~uncopiable() {}
    uncopiable(const self&) = delete;
    self& operator=(const self&) = delete;
};

struct A {
    struct B : uncopiable {
        using self = B;

        B() {
        }

        B(B&&) {};
        self& operator=(B&&) = default;

        ~B() {
        }
    };

    A() { v.emplace_back(); }
    ~A() {}

private:
    std::vector<B> v;
};

int main () {
    A* a = new A();
}
person EvgeniyZh    schedule 11.10.2015