Статическое утверждение для конструктора перемещения, отличного от конструктора копирования

Представьте, что у меня есть класс A, который дешево перемещать и дорого копировать. Это может выглядеть как

class A {
  public: 
    [...]

  private:
    HeavyClass m;
};

Для этого класса я бы хотел, чтобы статическая проверка заключалась в том, что класс (1) движется конструктивно и (2) не просто использует конструктор копирования для перемещения конструкции, независимо от того, явно ли объявлен конструктор перемещения или нет.

Возможно ли это?

Что касается того, почему я хотел бы этого, рассмотрим следующий пример: сначала класс автоматически генерирует конструктор перемещения и ведет себя по желанию. Затем кто-то меняет класс и добавляет деструктор, из-за чего конструктор перемещения не должен быть неявно сгенерирован, а вместо него используется конструктор копирования.

Следовательно, a static_assert было бы идеальным, но кажется, что ни один из is_move_constructible или is_trivially_move_constructible не полезен здесь.

Кроме того, я знаю, что для всех таких классов возможно иметь A(A&&) = default;, но решение с static_assert было бы более чистым и позволяло выполнять проверку вне определения класса (fi в других проектах, основанных на этом класс).

ИЗМЕНИТЬ:
Я не хочу запрещать создание копии, я просто хотел бы убедиться, что конструктор перемещения не использует его...

Ответ 1

Если вы можете изменить A на наличие косвенности, вы можете сделать следующее:

template <bool>
struct MoveOnly {
    MoveOnly() = default;
    ~MoveOnly() = default;
    MoveOnly(const MoveOnly&) = delete;
    MoveOnly(MoveOnly&&) = default;
    MoveOnly& operator=(const MoveOnly&) = delete;
    MoveOnly& operator=(MoveOnly&&) = default;
};

template <> struct MoveOnly<false> {};

template <bool check = false>
class A_Impl : MoveOnly<check> {
public: 
    // ... as ~A_Impl() {}
    // ...
private:
    HeavyClass m;
};

using A = A_Impl<false>; // Normal case

// The check
static_assert(std::is_move_constructible<A_Impl<true>>::value, "");

Демо

Ответ 2

Генерация экземпляра копирования и оператора присваивания копии устарела, когда объявлен деструктор. Нет необходимости в static assert или templates, это часть современного С++.

Решение состоит в том, чтобы разрешить предупреждения об устаревании

Также включите предупреждения в ошибки, если вы еще этого не сделали.

Таким образом, вам не нужно забывать добавлять статические утверждения во всем. Кроме того, вам по-прежнему разрешено добавлять деструкторы, иметь неперемещаемые элементы и наследовать от недвижных, если вы не копируете и не перемещаете какие-либо экземпляры таких объектов. Нет ограничений в отношении, если они не применяются.

С помощью этой настройки вы можете попробовать добавить этот код к вашему классу (типичная регрессия)

virtual ~A() = default;

Если вы попытаетесь переместить или скопировать экземпляр A, то компиляция завершится неудачно.

Пример сообщения об ошибке clang-3.9 -Werror -Wdeprecated

main.cpp:13:13: error: definition of implicit copy assignment
operator for 'A' is deprecated because it has a user-declared 
destructor [-Werror,-Wdeprecated]
    virtual ~A() = default;
            ^
main.cpp:21:7: note: implicit copy assignment operator for
'A' first required here
    b = std::move(a);
      ^
1 error generated.

Если вы создаете экземпляр A и просто передаете его на const reference, то никаких жалоб от вашего компилятора не будет.

Ответ 3

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

Одним из общих решений было бы использование статического анализатора для обеспечения Rule of Zero для всех классов.

Ссылка на вопрос SO об этом. Однако добавление параметров компилятора как описано в моем другом ответе, является лучшим решением, если вы его компилируете.