C запретить конструктор копирования

Обновлено: 26.04.2024

Есть класс, надо запретить копирование его экземпляров. Можно как то сделать, чтоб в интерфейсе класса вообще не было конструктора копии и оператора копирующего присваивания?

Как запретить копирование с флешки?
Доброе утро! Поскажите, пожалуйста, как запретить копирование с флешки? Хочу распечатать.

Как запретить копирование диска?
Люди добрые . Мучаюсь уже третий день не как не могу сделать так чтобу ДВД видео могли увидеть.

Как обойти "запретить копирование"
Вкладка Параметры доставки - основные - галочка на запрет копирования. У получателя соответственно.

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

Можно как то сделать, чтоб в интерфейсе класса вообще не было конструктора копии и оператора копирующего присваивания?

В С++11 появились способы запретить копирование без трюков. Вышеприведенный код можно переписать так:

Вам нужно что то типа = delete ?

Или вы вообще не хотите делать никаких упоминаний о конструкторе копии, а компилятор должен сам прочитать ваши мысли и догадаться?

Раньше ещё можно было запилить "сишный сингелтон":

Копирование невозможно было организовать, потому что структура безымяная, а значит тип был неизвестен. А значит нельзя создать объект неизвестного типа, а значит нельзя запустить конструктор.

Но сегодня извлечь информацию о типе можно из имени объекта:

Поэтому, трюки "без явных запретов" больше не работают.

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

Можно так же агрегировать объект такого класса. Но это тупо.

Экземпляров может быть куча, а не один. просто их нельзя копировать.

Добавлено через 2 минуты

А дефолтный то конструктор почему протектед?

Добавлено через 1 минуту

Сингелтон есть класс, у которого всего один экземпляр. А здесь экземпляров много, но ни один не может быть ни декларирован как копия другого, ни присвоен другому экземпляру.

Чтобы запретить создание объектов noncopyable вне контекста наследования, т.к. это не имеет смысла. Очевидно, что это утилитный класс у которого одна цель, сделать наследника некопируемым.

Перефразирую: цель класса noncopyable — сделать его наследников некопируемыми. Создание объектов самого класса noncopyable смысла не имеет, т.к. это пустышка.

С чего Вы взяли, что наследники вообще есть? Их нет, так как именно они не имеют смысла. А по-Вашему получается, что есть класс, не имеющий смысла и имеющий таких же бессмысленных потомков, у которых есть свои бессмысленные потомки и так до бесконечности.

В Вашем фрагменте у него нет потомков. В моём проекте у обсуждаемого класса также нет потомков, так как они не имеют смысла. По Вашему если экземпляр нельзя копировать, то класс - пустышка и ему нельзя иметь ни одного экземпляра, а запретить копирование надо не его экземпляров, а экземпляров потомков? Ну так замкнутый круг до бесконечности, порочным называемый: так как экземпляры потомков запрещено копировать, то по-Вашему они также не имеют смысла, это пустышки и запретить копирование надо уже для их потомков, но они тоже - пустышки по той же причине.

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

Эта небольшая заметка о том, как с приходом нового стандарта C++11 изменились требования стандартных контейнеров к своим элементам. В C++98 от элемента контейнера требовалось, по сути, наличие «разумных» конструктора копирования и оператора присваивания. Если, например, объект вашего класса владеет каким-либо ресурсом, копирование обычно становится невозможным (по крайней мере, без «глубокого» копирования ресурса). В качестве примера давайте рассмотрим следующий класс-обертку вокруг FILE* , написанную на C++98:

Мы запретили копирование и присваивание объектов этого класса, поскольку копирование FILE* потребовало бы некоторых платформо-зависимых ухищрений, и вообще не имеет особого физического смысла.

Что же делать, если требуется хранить целый список объектов типа File ? К сожалению, мы не можем использовать File в стандартном контейнере, то есть такой код просто не скомпилируется:

Типичным решением такой проблемы в C++98 является использование shared_ptr :


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

Если мы разрешаем использование C++11, то картина сильно меняется. С появлением move semantics, стандартные контейнеры больше не требуют наличия обычных конструктора копирования и оператора присваивания, если только вы не собираетесь копировать контейнер целиком. Вместо них достаточно наличия семантики перемещения. Давайте посмотрим, как мы можем переписать пример с классом File на C++11:

Мы снова запрещаем обычное копирование, но разрешаем перемещение объекта. Теперь такой код работает:

Кроме того, благодаря variadic templates, в контейнерах появилась новая шаблонная функция emplace_back , которая позволяет создать объект прямо в контейнере, не копируя его:

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

SHALL NOT DANCE THERE

Довольно часто можно встретить код на C++, в котором у одного или нескольких классов конструктор копирования и оператор присваивания объявлены private и написан комментарий вида «копирование запрещено».

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

Рассмотрим возможные проблемы.

Сначала – краткий экскурс, зачем нужен этот прием.

Если программа пытается скопировать объект класса, компилятор C++ по умолчанию автоматически генерирует конструктор копирования или оператор присваивания, если они не объявлены в классе явно. Автоматически сгенерированный конструктор выполняет почленное копирование, а автоматически сгенерированный оператор присваивания – почленное присваивание.

Вот простой пример:

возникнет неопределенное поведение. Будет вызван сгенерированный компилятором конструктор копирования, который скопирует указатель. В результате оба объекта будут хранить указатели с равными адресами. Первым отработает деструктор объекта second, он выполнит delete[], затем будет вызван деструктор объекта first, он попытается выполнить delete[] повторно для того же адреса, и это приведет к неопределенному поведению.

Решение вроде бы очевидно – реализовать конструктор копирования и оператор присваивания с правильным поведением. Например, при копировании новый объект создает свой массив и копирует в него данные из старого.

Это не всегда верный путь. Не все объекты по своей сути подлежат копированию.

Например, объект может хранить дескриптор открытого файла в виде целого числа. Деструктор «закрывает файл» с помощью функции операционной системы. Очевидно, почленное копирование не подойдет. А что должно происходить при копировании? Должен ли файл открываться еще раз? Обычно в этом случае копирование не имеет смысла.

Другой пример – класс для захвата критической секции при создании объекта такого класса. Какой смысл копировать объект? Секция уже захвачена.

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

Если копирование объекта не имеет смысла, нужно сделать так, чтобы компилятор не смог случайно его выполнить. Для этого обычно делают так:


Все три способа встречаются в реальном коде.

Казалось бы, чем второй и третий варианты отличаются от первого? Модификатор private в любом случае не даст вызвать копирование.

Функции-члены того же класса могут вызывать конструктор копирования и оператор присваивания, даже если те объявлены private. И «друзья» класса (friend) тоже могут. Никто не мешает написать в коде что-нибудь такое:


Теперь налицо разница между первым вариантом и остальными.

Во втором варианте будет срабатывать assert… при условии, что управление пройдет через этот код. Здесь многое зависит от того, насколько часто этот код вызывается, в частности, от покрытия кода тестами. Может быть, вы заметите проблему при первом же запуске, может быть – очень нескоро.

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

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

От ошибки помогает комментарий вида «запрещенные операции» или, если есть сомнения, «запрещенные операции, не определять под страхом увольнения». От злого умысла не поможет ничто – в C++ никто не мешает взять адрес объекта, привести его к типу char* и побайтово перезаписать объект как угодно.

В C++0x есть ключевое слово delete:


В этом случае не только определить, но и вызвать их будет невозможно – при попытке компиляции места вызова будет выдана ошибка компиляции.

Вариант «объявить и не определять» доступен и ранее C++0x, его, в частности, использует boost::noncopyable. Вариант наследоваться от boost::noncopyable или аналогичного своего класса тоже достаточно надежен и доступен в любой версии.

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

I have a doubt in your code will every time different instance be created I think GetUniqueInstance() will always give reference to same object.

3 Answers 3

You can make the copy constructor private and provide no implementation:

Or in C++11, explicitly forbid it:

Regarding the delete keyword I'd like to add the following. My current habit habit when designing a new class is to delete both the copy constructor and assignment operator immediately. I've found that, depending on context, they're mostly unnecessary and deleting them prevents some cases of unexpected behavior. If a situation occurs where a copy ctor may be needed, determine if it can be done with move semantics. If this is undesirable, provide an implementation for both(!) the copy ctor and assignment operator. Whether this is a good approach I'll leave up to the reader.

@pauluss86 I like your approach but I wouldn't fully commit to it as I think the time spent following this pattern is greater than time saved by the errors it prevents. I simply forbid copy whenever not sure.

@pauluss86 This is basically what Rust does: Move-by-default (and const-by-default). Very helpful in my opinion.

If you don't mind multiple inheritance (it is not that bad, after all), you may write simple class with private copy constructor and assignment operator and additionally subclass it:

For GCC this gives the following error message:

I'm not very sure for this to work in every compiler, though. There is a related question, but with no answer yet.

UPD:

In C++11 you may also write NonAssignable class as follows:

The delete keyword prevents members from being default-constructed, so they cannot be used further in a derived class's default-constructed members. Trying to assign gives the following error in GCC:

UPD:

Boost already has a class just for the same purpose, I guess it's even implemented in similar way. The class is called boost::noncopyable and is meant to be used as in the following:

I'd recommend sticking to the Boost's solution if your project policy allows it. See also another boost::noncopyable -related question for more information.

Что мне нужно делать отсюда, чтобы Foo нельзя было копировать?

10 ответы

РЕДАКТИРОВАТЬ: версия C ++ 11, если у вас есть компилятор, поддерживающий эту функцию:

Создан 22 июля '20, 10:07

Интересно, почему вы сделали конструктор по умолчанию закрытым и добавили метод create ()? Какие преимущества у этого макета? - Внештатный консультант

Отключение оператора построения копий и присваивания копий также отключает конструирование и присваивание перемещений. Операции перемещения будут по-прежнему функционировать, возвращаясь к копированию. Повторно включите их, явно установив для них значение «по умолчанию». Что-то, о чем нужно знать. - ясень

@Ash - важный улов, но как переместить откат к копированию, если копия уже удалена? - Ник

Рекомендуется размещать удаленные методы в общедоступном разделе. - Джордан

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


Но как однажды сказал Скотт Мейерс . «Это прекрасный класс, просто я нахожу это имя немного неестественным, ошибочно неестественным» или что-то в этом роде.

Есть ссылка на контекст цитаты? - GManNickG

Ссылка: Эффективный C ++ (третье издание) - Скотт Мейерс, пункт 6 - Тирлер

Еще один способ запретить конструктор копирования. Для удобства можно использовать макрос DISALLOW_COPY_AND_ASSIGN:

Затем в классе Foo:

ответ дан 28 окт '16, 07:10

Ваше решение не работает как есть с некоторыми компиляторами. Некоторые компиляторы C ++ требуют, чтобы если вы объявляли функцию-член класса, вы также должны были определить ее, даже если она никогда не использовалась в коде. Таким образом, вам нужно использовать <> с двумя описанными выше функциями. - три бита

@ThreeBit, если вы имеете в виду конструктор с одним параметром и деструктор, говоря «две функции», это деклерация, и программист уже знает, что они будут иметь определение где-то еще. В остальном это такой же, как принятый ответ. - Фредрик Гаусс

@ThreeBit: Какие компиляторы вы имеете в виду? Если они это сделают, они не соответствуют стандарту. - Себастьян Мах

@ Себастьян, это неправда. Если объявлен конструктор класса, он должен быть где-то определен. Тот факт, что некоторые популярные компиляторы позволяют опускать определение функции-члена класса, если функция не вызывается явно, является расширением компилятора для языка. Компилятор Green Hills C ++ является примером строгого в этом отношении. Я не могу найти в Стандарте C ++ места, которое требует от компиляторов пропускать привязку функции-члена, если она не используется. Не стесняйтесь найти такой пункт. Возможно, тебе удастся его найти. - три бита

@ThreeBit: Интересное обсуждение. Что нашел в стандарте: "Будет в большинстве одно определение не встроенной функции-члена в программе; никакой диагностики не требуется ".. Затем для местных классов: "Функции-члены локального класса должны быть определены встроенными в определение их класса, если они вообще определены.". Я не нахожу ничего, что запрещало бы объявления функций-членов, не имеющие соответствующего определения. - Себастьян Мах

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

То же самое, конечно, и с классами.

Чтобы добавить туда немного.

Традиционное решение, как уже было сказано, состоит в том, чтобы объявлять и то и другое Copy Constructor и Assignment Operator as private и не в определять их.

  • Потому что они private , это приведет к ошибка времени компиляции от любого, кто пытается их использовать, у кого нет доступа к закрытым частям класса .
  • Что оставляет друзей (и сам класс), для которых ошибка будет возникать в виде undefined symbol либо в ссылка времени (если вы проверите их там) или, скорее всего, в время выполнения (при попытке загрузить библиотеку).

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

Кроме того, стоит отметить, что эти свойства являются транзитивными по пути наследования и композиции: компилятор будет генерировать только версии по умолчанию для Default Constructor , Copy Constructor , Assignment Operator и Destructor если можно.

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

Вот почему наследование от этого класса (или использование его в качестве атрибута) эффективно предотвращает копирование или присвоение вашего собственного класса, если вы сами не определите эти операторы.

Обычно здесь наследование выбирается вместо композиции по двум причинам:

  • Объект эффективно Uncopyable , даже если полиморфизм может оказаться не таким полезным
  • Наследование приводит к EBO or Empty Base Optimization , хотя атрибут будет адресуемым и, таким образом, будет занимать память (в каждом экземпляре класса), даже если он на самом деле не нужен, компилятор имеет возможность не добавлять эти накладные расходы для базового класса.

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

Читайте также: