С явно удаленными функциями-членами в C ++ 11 все еще стоит наследовать от некопируемого базового класса? - PullRequest
58 голосов
/ 27 февраля 2012

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

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

Преимущества, выдвинутые в этом вопросе , по-прежнему применимы к C ++ 11?


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

В C ++ 03:

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

В C ++ 11:

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

EDIT:

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

Ответы [ 5 ]

75 голосов
/ 27 февраля 2012

Ну, это:

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

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


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

Это не столько "проще", сколько "легче усваивается".

Учтите это:

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

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

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

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

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

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

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

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

24 голосов
/ 27 февраля 2012

Во-первых, как отмечают другие до меня, вы неправильно поняли идиому, вы объявляете частным и не определяете :

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

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

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

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

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

10 голосов
/ 13 августа 2012

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

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

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

5 голосов
/ 27 февраля 2012

Он более читабелен и позволяет компилятору показывать лучшие ошибки.

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

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

2 голосов
/ 08 января 2014

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

...