Запрещение присваивания и передачи по значению - PullRequest
11 голосов
/ 30 января 2012

Насколько я понимаю, я могу "отключить" копирование и присваивание моим объектам, определив конструктор частной копии и оператор присваивания:

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

Но как это использовать?
это считается плохой практикой?

Я был бы признателен, если бы вы описали ситуацию, в которой было бы разумно / полезно "отключить" присвоение и копировать конструктор таким образом.

Ответы [ 5 ]

11 голосов
/ 30 января 2012

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

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

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

5 голосов
/ 30 января 2012

Вообще говоря, любой класс, который управляет ресурсом, должен быть недоступен для копирования или иметь специальную семантику копирования. Обратное также верно: любой класс, который не подлежит копированию или нуждается в специальной семантике копирования, управляет ресурсом. «Управлять ресурсом» на языке C ++ на практике означает ответственность за некоторое пространство в памяти, или за подключение к сети или базе данных, или за дескриптор файла, или за отмену транзакции и т. Д.

Управление ресурсами содержит довольно много примеров. Это обязанности, которые выполняют префиксную операцию, суффиксную операцию и, возможно, некоторое промежуточное действие. Например, управление памятью включает в себя получение дескриптора адреса памяти, которым мы будем управлять, возможно, возиться с этой памятью, и, наконец, освободить дескриптор (потому что, если вы что-то любите, пусть это будет бесплатно).

template<typename T>
struct memory {
    memory(T const& val = T()) : p(new T(val)) { } 
    ~memory() { delete p }
    T& operator*() const { return *p; }
private:
    T* p;
};

// ...
{
    memory<int> m0;
    *m0 = 3;
    std::cout << *m0 << '\n';
}

Этот класс memory является почти правильным: он автоматически получает базовое пространство памяти и автоматически освобождает его, даже если исключение распространяется через некоторое время после того, как оно получило свой ресурс. Но рассмотрим этот сценарий:

{
    memory<double> m1(3.14);
    memory<double> m2(m1);  // m2.p == m1.p (do you hear the bomb ticking?)
}

Поскольку мы не предоставили специализированную семантику копирования для memory, компилятор предоставляет собственный конструктор копирования и назначение копирования. Они делают неправильную вещь: m2 = m1 означает m2.p = m1.p, так что два указателя указывают на один и тот же адрес. Это неправильно, потому что когда m2 выходит из области видимости, он освобождает свой ресурс как хороший ответственный объект, а когда m1 затем выходит из области видимости, он тоже освобождает свой ресурс, тот же самый ресурс m2 уже освобожден, завершая двойной -delete - пресловутый сценарий неопределенного поведения. Более того, в C ++ чрезвычайно легко делать копии объекта, даже не замечая: функция принимает свой параметр по значению, возвращает свой параметр по значению или принимает свой параметр по ссылке, но затем вызывает другую функцию, которая сама принимает (или возвращает) свой параметр по значению ... Проще всего предположить, что вещи будут пытаться скопироваться.

Все это говорит о том, что когда класс 'raison d'être управляет ресурсом, вы сразу должны знать, что вам нужно обрабатывать копирование. Вы должны решить

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

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

2 голосов
/ 30 января 2012

Это довольно распространенная практика.Существует множество примеров, когда копирование не подходит.

Допустим, ваш объект представляет собой открытый сокет на стороне сервера (т.е. входящее сетевое соединение);Какова будет семантика создания копии этого объекта?

0 голосов
/ 03 февраля 2012

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

также проверьте это: Класс Singleton в C ++

0 голосов
/ 30 января 2012

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

...