С явно удаленными функциями-членами в С++ 11, стоит ли наследовать от неготовного базового класса?

С явно удаленными функциями-членами в С++ 11, стоит ли наследовать от неготовного базового класса?

Я говорю об трюке, в котором вы частно наследуете базовый класс, который имеет закрытый или удаленный экземпляр копии и назначение копии (например, boost::noncopyable).

Являются ли преимущества в этом question доступными для С++ 11?


Я не понимаю, почему некоторые люди утверждают, что проще сделать класс не скопированным в С++ 11.

В С++ 03:

private:
    MyClass(const MyClass&) {}
    MyClass& operator=(const MyClass&) {}

В С++ 11:

MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;

EDIT:

Как отмечали многие люди, было ошибкой предоставлять пустые тела (например, {}) для частного конструктора копирования и оператора присваивания копии, поскольку это позволило бы самому классу вызывать эти операторы без каких-либо ошибок. Сначала я начал не добавлять {}, но столкнулся с некоторыми проблемами компоновщика, которые заставили меня добавить {} по какой-то глупой причине (я не помню обстоятельств). Я знаю, что лучше знаю.: -)

Ответ 1

Ну, это:

private:
    MyClass(const MyClass&) {}
    MyClass& operator=(const MyClass&) {}

По-прежнему технически позволяет MyClass копироваться членами и друзьями. Конечно, эти типы и функции теоретически находятся под вашим контролем, но класс по-прежнему можно копировать. По крайней мере, с boost::noncopyable и = delete никто не может скопировать класс.


Я не понимаю, почему некоторые люди утверждают, что проще сделать класс не скопированным в С++ 11.

Это не так "проще", как "более легко усваиваемое".

Рассмотрим это:

class MyClass
{
private:
    MyClass(const MyClass&) {}
    MyClass& operator=(const MyClass&) {}
};

Если вы программист на С++, который прочитал вводный текст на С++, но мало подвержен идиоматическому С++ (т.е. много программистов на С++), это... запутанно. Он объявляет конструкторы копирования и операторы присваивания копий, но они пусты. Так зачем вообще объявлять их? Да, они private, но это только вызывает больше вопросов: зачем делать их частными?

Чтобы понять, почему это предотвращает копирование, вы должны понимать, что, объявив их конфиденциальными, вы делаете это так, чтобы не члены/друзья не могли его скопировать. Это не сразу понятно новичкам. Также это сообщение об ошибке, которое они получат, когда они попытаются скопировать его.

Теперь сравните его с версией С++ 11:

class MyClass
{
public:
    MyClass(const MyClass&) = delete;
    MyClass& operator=(const MyClass&) = delete;
};

Что нужно, чтобы понять, что этот класс нельзя скопировать? Не более чем понимание того, что означает синтаксис = delete. Любая книга, объясняющая правила синтаксиса С++ 11, точно скажет вам, что это делает. Эффект этого кода очевиден для неопытного пользователя С++.

Что хорошего в этой идиоме, так это то, что она становится идиомой, потому что это самый ясный, самый очевидный способ сказать именно то, что вы имеете в виду.

Даже boost::noncopyable требуется немного подумать. Да, он называется "не подлежащий копированию", поэтому он самодокументирован. Но если вы никогда этого не видели, это вызывает вопросы. Почему вы извлекаете из чего-то, что невозможно скопировать? Почему сообщения об ошибках говорят о конструкторе boost::noncopyable copy? И т.д. Опять же, понимание идиомы требует большего умственного усилия.

Ответ 2

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

class noncopyable {
   noncopyable( noncopyable const & );
   noncopyable& operator=( noncopyable const & );
};

Если тип возврата operator= может быть в принципе любым. На данный момент, если вы читаете этот код в заголовке, что он на самом деле означает? Его можно копировать только друзьями или его нельзя копировать когда-либо? Обратите внимание, что если вы даете определение, как в вашем примере, оно заявляет, что я могу быть скопирован только внутри класса и друзей, это отсутствие определения, которое переводит это, я не могу скопировать. Но отсутствие определения в заголовке не является синонимом отсутствия определения везде, поскольку его можно определить в файле cpp.

Здесь наследование от типа, называемого noncopyable, делает его явным, что цель состоит в том, чтобы избежать копирования, так же, как если бы вы вручную написали код выше, вы должны документировать с комментарием в строке, что намерение отключает копии.

С++ 11 ничего не меняет, он просто делает документацию явной в коде. В той же строке, в которой вы объявляете конструктор копии как удаленный, вы задокументированы, что хотите полностью отключить его.

В качестве последнего комментария функция С++ 11 состоит не только в том, что она может писать noncopyable с меньшим количеством кода или лучше, а скорее из-за запрещения компилятором генерировать код, который вы не хотите сгенерировать. Это всего лишь одно использование этой функции.

Ответ 3

В дополнение к тем, которые другие подняли...

Наличие частного конструктора копирования и оператора присваивания копии, который вы не определяете, не позволяет кому-либо создавать копии. Однако, если функция-член или функция-другу пытается создать копию, они получат ошибку времени ссылки. Если они попытаются сделать это, когда вы явно удалили эти функции, они получат ошибку времени компиляции.

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

Ответ 4

Это более читаемо и позволяет компилятору давать лучшие ошибки.

"Удаленный" более понятен читателю, особенно если он просматривает класс. Аналогично, компилятор может сказать вам, что вы пытаетесь скопировать не скопируемый тип, вместо того, чтобы давать вам общую проблему с попыткой доступа к частному члену.

Но на самом деле это просто удобная функция.

Ответ 5

Люди здесь рекомендуют объявлять функции-члены без их определения. Я хотел бы отметить, что такой подход не переносимый. Некоторые компиляторы/компоновщики требуют, чтобы, объявив функцию-член, вы также должны определить ее, даже если она не используется. Если вы используете только VС++, GCC, clang, тогда вы можете избежать этого, но если вы пытаетесь написать действительно портативный код, то некоторые другие компиляторы (например, Green Hills) не сработают.