Запрещение присвоения и передачи по значению

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

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

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

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

Ответ 1

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

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

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

Ответ 2

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

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

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 - пресловутый сценарий поведения undefined. Более того, на С++ очень легко сделать копии объекта, даже не заметив: функция, берущая свой параметр по значению, возвращающий свой параметр по значению или принимая его параметр по ссылке, но затем вызывающий другую функцию, которая сама принимает (или возвращает) ее параметр по значению... Легче просто предположить, что все будет пытаться скопировать.

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

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

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

также проверьте это: Singleton Class в С++