Pull to refresh

Comments 45

бустовский вариант:
namespace noncopyable_  // protection from unintended ADL
{
  class noncopyable
  {
   protected:
      noncopyable() {}
      ~noncopyable() {}
   private:  // emphasize the following members are private
      noncopyable( const noncopyable& );
      const noncopyable& operator=( const noncopyable& );
  };
}
Блин, всегда писал EPIC FAIL код.
Ну что ж, хорошо что на плюсах я пишу только для себя :)
Другой пример – класс-скобка для захвата критической секции


Если не секрет, откуда термин «класс-скобка»?
После тщательного гугления приходится прийти к выводу, что «термин» порожден мозгом автора. Из текста изведен.
Возможно, имелся в виду класс-обертка, «wrapper class».
Нет, речь именно о классе, объекты которого создаются на стеке. К объектам классов-оберток такого требования нет — можно поле другого класса, например, сделать.
Мне кажется, речь идет о RAII-классе
Это он, да, но есть ли для него русскоязычный термин?
Согласно википедии, сам метод переводится «Получение ресурса есть инициализация». Не встречал общепризнанного перевода RAII-класса, могу предложить лишь свой: «владелец ресурса», но он мне нравится меньше, чем «RAII-класс».
Кому любопытно узнать пару фактов об автогенерируемом конструкторе копирования, тут вот когда-то накидал небольшую почеркушку: Автогенерируемый конструктор копирования в С++. Когда-то вот в стандарте копался из любопытства и любви к деталям…
Почитал ваш пост по ссылке и я видать сильно туплю, но я не понимаю почему

struct A {
  A() {};
  A(A&) {};
};

struct B : public A {
  B() {};
};


int main() {
  const B b1;
  B b2 = b1;
}

Вызывает ошибку.

Ведь вы же сами написали
«Неявно декларированный конструктор копирования для класса X имеет форму X::X (const X&) в том случае, если:
- каждый прямой или виртуальный базовый класс B класса X имеет копирующий конструктор, чей первый параметр - const B& или const volatile B& , и
- у каждого нестатического члена класса X, который имеет тип M (или массив таковых) имеет конструктор копирования первый аргументом которого является const M& или const volatile M&.
!!!
Во всех остальных случаях он имеет форму X::X (X&).
!!!
Конструктор копирования всегда является inline public членом класса»


Так что же мешает компилятору вызвать конструктор копирования
B(B&){};


Если он сам же его и сгенерировал?
Либо вы не правильно интерпретировал стандарт, либо это FAIL в стандарте.
Охтыж демонический разврат
const B b1;


сори :(
имхо, никакого разврата тут нет. вы же пишете
const int i = 5
, чем эта ситуация? Класс B может быть чем угодно. Если представить, что это Complex, то становится немного логичнее?
это я к тому что понял свою ошибку :) почему вот это конструктор копирования B(B&){}; не подходит.
опечатка:
«чем эта ситуация?» = «чем эта ситуация отличается?»
никакого фейла в стандарте, все логично. Если вы явно сделали аргумент копирующего конструктора не константным (изменямым, это очень важно), то полагается, что вы будете его менять во время копирования (возможно это вам действительно необходимо, возможно вы ошиблись, возможно вас просто переклинило в тот момент). Если вы хотите менять копируемый объект во время копирования, то туда нельзя передавать const-объект. Где тут фейл? все логично. Компилятор не есть ясновидящий, он не знает чего вы хотите (и действительно ли вы хотите чего-то, а не просто напарили), поэтому ему только и остается следовать правилам.
Функции-члены того же класса могут вызывать конструктор копирования и оператор присваивания, даже если те объявлены private. И «друзья» класса (friend) тоже могут.

Обычный паттерн — это как в бусте — объявить простой trait класс NonCopyable и от него наследоваться.
В таком случае никакие френды или функции в классах наследниках не смогут скопировать класс.

На самом деле еще лучше иметь два класса — NonCopyable и NonAssignable (запрещает только присваивание)… и до кучи NonInheritable (но этот уже использует несколько спорный трюк, и его не все платформы поддерживают, AFAIK).
В смысле, я на C++0x бочку не качу… а то минусовать начнете, как предателя :-)
C++0x forever!
А в чем отличие предложенного метода определения вручную конструктора копирования и оператора = от ключевого слова explicit?
При использовании ключевого слова explicit можно, не очень подумав, написать код, который явно вызывает копирование, этот код скомпилируется.
Картинка напомнила:

Всякая ОС — это минное поле:

Unix — единственное в мире поле, заминированное с соблюдением всех обычаев войны, из-за своей уникальности приватизировано и обнесено бетонным забором.

DOS — минное поле с табличкой “Миннет” (орфография оригинала).

Linux — минное поле, утыканное табличками “ACHTUNG MINEN!” через каждый метр

Red Hat Linux — минное поле, часть табличек на котором заменена знаками типа “череп и кости”

ASP Linux — минное поле со знаками и табличками на русском языке

Red Flag Linux — минное поле, огражденное красными флажками и табличками с двумя иероглифами “мин нет”.
(Вышеупомянутые поля разминируют саперы-добровольцы. Обезвреживание мин проводится на открытой местности. С каждым днем их запасы тают, но пополняются и списки погибших саперов, которые периодически вывешивают на табличках)

FreeBSD — открыто заминированный оазис.

OpenBSD — открыто заминированный остров.

NetBSD — открыто заминированная поляна в лесу.
(Разминируются на месте в ночное время ветеранами-волонтерами)

Windows 95 — стадион на минном поле.

Windows 98 — школьный стадион на минном поле.

Windows Me — стадион спецшколы для умственно отсталых на минном поле.

Windows XP Home Edition — лагерь беженцев посреди минного поля.

Windows XP Professional — лагерь саперов посреди минного поля.

Windows 2000 — лагерь саперов, которые знали о том, что они находятся на минном поле.

Windows NT 4 — лагерь саперов на заминированном острове.

Windows Server 2003 — склад ГСМ на минном поле.

Windows Longhorn — кампания по принудительному минированию личных дачных участков во имя безопасности.
(На данных полях все мины тщательно замаскированы и присыпаны землей; за вынос мин с поля расстрел; обнаружение мин с последующим докладом расценивается как попытка посеять панику среди мирного населения; взрывы мин с человеческими жертвами официально считаются провокациями террористов; погибших хоронят в цинковых гробах, вскрытие которых запрещено)

Nowell Netware — заминированная железная дорога.

BeOS — заминированное болото.

Lindows — заминированный туннель под Ла-Маншем.

MacOS — минное поле на Луне.

MacOS X — минное поле на Луне с табличками для случайных прохожих.
> Автоматически сгенерированный конструктор выполняет
> почленное копирование
Стоп. Все- таки почленное или побитовое? Что будет, например, если соеди членов класса есть string или какой-нибудь другой объект — он будет скопирован с использованием его собственного конструктора копирования, или все же будет скопирован побитово (и в итоге все сломается)?

Т.е. eсли у меня есть простейший класс-структура, внутри которого члены — все std:string и int, то нужно ли мне заморачиваться с конструктором копирования? Я всю жизнь писал для них конструктор копирования (или глушил его через private)…
Почленное копирование, не побитовое. Так что зря вы всю жизнь писали пользовательские конструкторы.
Ну так в этом случае ситуаций, когда нужно запрещать копирование из-за боязни получить неопределенное поведение, довольно мало. (Почленное копирование же рекурсивно.) Они сводятся к примерам в этой статье — указатели и ссылки на внешние ресурсы, освобождаемые в деструкторе. Если везде использовать shared_ptr (обойдем пока стороной вопрос производительности — в большинстве случаев имеет место случай экономии на спичках), то это уже поборет значительную часть проблем. А если еще и все свести к идеологии «выделение ресурса есть инициализация», то не будет ни деструкторов, ни, соответственно, проблем.

Кстати, как iostream относится к копированию? Там есть счетчик ссылок, препятствующий преждевременному закрытию файла?
Есть класс A, в котором объявлен член класса shared_ptr m_b.
Вы создали экземпляр класса A, затем скопировали.
Теперь обе копии класса A будут внутри ссылаться на общий объект класса B.
Это чревато последствиями, если в классе B есть функции модификации содержимого.
А в много поточной среде придется еще и синхронизацию доступа к общим данным прикручивать.
shared_ptr здесь не панацея.
Странно, я никаких тегов для жирного шрифта не добавлял, видать парсер хабра заглючил…
Вы написали
 shared_ptr<B>

Это был тег :)
Да я потом уже догадался :)
Извиняюсь, забыл нажать «Предпросмотр»…
Ну раз вы решили не владеть объектом, а просто на него ссылаться, то уж будьте готовы к тому, что кто-то поменяет его без вашего ведома. И нет разницы, будет ли это объект того же класса А, или кто-то ещё.
Автор предыдущего комментария, на который я ответил, считает, что если он будет бездумно все члены в классе объявлять через shared_ptr, то это решит проблему конструктора копирования по умолчанию.

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

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

Копирование примитивных типов побитовое, это частный случай. Большинство проблем в описанном сценарии возникает из-за почленного копирования указателей и дескрипторов, которые обычно примитивных типов. Отсюда и ложное противопоставление «глубокого» и побитового копирования.

На самом деле при отсутствии объявленного конструктора копирования (для «глубокого» копирования) выполняется почленное копирование, которое для примитивных типов реализовано как побитовое. Так что на самом деле выбор не между «глубоким» и побитовым копированием, а между «глубоким» и почленным.
И не говорите. 15 лет пишу на c++ (правда, это не основной мой язык), а такое очевидное заблуждение все время считал истиной. Стыдно жуть как.
Так язык к этому располагает. Как только нужно что-то чуть более сложное, чем простое копирование, нужно ручками всё остальное почленно скопировать… Возможно, оттуда и иллюзия.
Интересно, что из-за RVO MSVC легко «копирует» возвращаемый из функции объект.

То есть, допустим у нас есть класс Foo и у него запрещено копирование.
И есть метод Foo MakeFoo { return Foo(1); } — ну написали, забыв, что у нас запрещено копирование — компилятор должен такие вещи помнить.
Так вот студия легко скомпилирует код Foo x = MakeFoo(), ибо копирования тут нет.
А вот в гцц этот код не соберется и тут-то мы замечаем, что у нас проблемы с переносимостью и нужно переделывать через void MakeFoo(Foo* result)…
Как показывает опыт вот этого поста, активное плюсование запросов в MS Connect заметно способствует скорому исправлению.
А, да пофиг уже. RVO отмирает как класс. Move semantics же.

Впрочем, rvo может быть полезен для древнего кода, в который никто не будет добавлять move semantics, да.
Теперь надо статью про то, как таки сделать правильное strong-exception-safe копирование :)
Ожидал какого-то подвоха, но оказалось все скучно примитивно и основная рекомендация делать так, как я использую у себя в проектах => private и без реализации.
Обычно в таких случая пишут что статья для новичков.
Хотя конечно для них это весьма полезно.
А может быть есть способ при такой реализации
// NOT BAD
class NonCopyable {
// blahblahblahpublic:
private:
   // copy and assignment prohibited
   NonCopyable( const NonCopyable& );
   void NonCopyable::operator=( const NonCopyable& );
};


заменить текст ошибки компилятора (или снабдить его дополнительным варнингом) о том, что это не просто забыли дописать тело, а что это именно запрет копирования объектов этого класса.
Sign up to leave a comment.