Внимательный читатель наверняка заметил в посте про Универсальные потоки на С++ для Windows и UNIX следующий фрагмент кода:
class Thread { public: ... private: ... // Защита от случайного копирования объекта в C++ Thread(const Thread&); void operator=(const Thread&); };
Это определения конструктора копирования и перегруженого оператора присваивания. Причем непосредственно реализаций этих функций нигде нет, только определения. Вопрос: для чего все это?
Давайте разберемся с назначением этих функций. Их прямая задача уметь копировать объект данного класса. А что произойдет, если вы по какой-то причине не определили конструктор копирования или оператор присваивания (может просто забыли), а пользователь вашего класса решил скопировать объект, возможно даже неосознанно? В этом случае компилятор сам определит конструктор копирования по умолчанию, который будет тупо копировать объект байт за байтом без учета смысла копируемых данных. И вам крупно повезет, если все члены-данные вашего класса являются либо базовыми типами (int
, long
, char
и т.д.), либо имеют корректные конструкторы копирования и операторы присваивания. В этом случае все будет хорошо — базовые типы компилятор умеет копировать правильно, а сложные типы скопируют себя сами через их конструкторы копирования. А представьте, что вы внутри своего класса создаете объекты динамически в куче и храните в классе только указатели на них. Указатеть — это базовый тип, и компилятор его нормально скопирует. А вот данные, на которые этот указатель указывает он копировать не будет. В итоге два объекта (старый-оригинал и новый-копия) будут ссылаться на один кусок памяти в куче. Теперь понятно, что при попытке освобождения этой памяти в деструкторе (если вы не забыли этого сделать) кто-то из этих двух объектов попытается освободить уже освобожденную память. Вероятность аварийного завершения программы в этом случае крайне высока, а поиск подобных ошибок может быть крайне долгим и мучительным.
Отсюда мораль: если для вашего класса не заданы конструктор копирования и оператор присваивания (они вам не нужны по смыслу), сделайте им пустые объявления в разделе закрытом разделе (private
). Тогда попытка скопировать этот объект сразу приведет к ошибке при компиляции. Во-первых, объявления являются закрытыми (private
), и сторонний пользователь вашего класса получит ошибку доступа к закрытым данным класса. Во-вторых, у этих функций нет тел, а значит вы сами не выстрелите себе в ногу, попытавшись случайно скопировать объект данного класса в нем же самом (тут вам private
уже не помеха), если вы на это не рассчитывали при проектирование класса.
Лично я делаю так. У меня есть следующий файл (ctorguard.h
):
#ifndef _EXT_CTOR_GUARD_H #define _EXT_CTOR_GUARD_H #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&); \ void operator=(const TypeName&) #endif Теперь определение класса будет выглядеть так: #include "ctorguard.h" class Thread { public: ... private: ... // Защита от случайного копирования объекта в C++ DISALLOW_COPY_AND_ASSIGN(Thread); };
Теперь вы надежно предохранены.
Кстати, вдогонку. При реализации оператора присваивания надо обязательно проверять — не пытаетесь ли вы копировать объект сам в себя, то есть, не является ли источник копирования самими объектом куда идет копирование. Если это произойдет, вы легко можете получить переполнения стека как самый вероятный исход из-за бесконечного вызова оператора присваивания себя самим.