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);
};
Теперь вы надежно предохранены. Кстати, вдогонку. При реализации оператора присваивания надо обязательно проверять не пытаетесь ли вы копировать объект сам в себя, то есть, не является ли источник копирования самими объектом куда идет копирование. Если это произойдет, вы легко можете получить переполнения стека как самый вероятный исход из-за бесконечного вызова оператора присваивания себя самим.
Можно воспользоваться наследованием boost::non_copyable
ОтветитьУдалитьionial: Конечно можно. boost хорош, но многие "родные" компиляторы типа AIX'вого или HP-UX'ного STL-то понимают по-разному, а про boost вообще говорить не приходится.
ОтветитьУдалить