Какая польза от частного конструктора копирования в C ++ - PullRequest
41 голосов
/ 25 июля 2011

Почему люди определяют личный конструктор копирования?

Когда делает конструктор копирования и оператор присваивания частным, хороший дизайн?

Если в классе нет членов, которые являются указателямиили обрабатывает уникальный объект (например, имя файла), а затем другие случаи, когда личный конструктор копирования является хорошей идеей?

Тот же вопрос применим к оператору присваивания.Учитывая, что большинство C ++ вращается вокруг копирования объектов и передачи по ссылке, есть ли хорошие проекты, которые включают конструктор private copy?

Ответы [ 7 ]

34 голосов
/ 25 июля 2011

Одним из вариантов использования является шаблон синглтона , где может быть только один экземпляр класса .В этом случае вам нужно сделать ваши конструкторы и оператор присваивания = частным, чтобы не было возможности создать более одного объекта.Единственный способ создать объект - использовать функцию GetInstance (), как показано ниже.

// An example of singleton pattern
class CMySingleton
{
public:
  static CMySingleton& GetInstance()
  {
    static CMySingleton singleton;
    return singleton;
  }

// Other non-static member functions
private:
  CMySingleton() {}                                  // Private constructor
  ~CMySingleton() {}
  CMySingleton(const CMySingleton&);                 // Prevent copy-construction
  CMySingleton& operator=(const CMySingleton&);      // Prevent assignment
};

int main(int argc, char* argv[])
{
  // create a single instance of the class
  CMySingleton &object = CMySingleton::GetInstance();

  // compile fail due to private constructor
  CMySingleton object1;
  // compile fail due to private copy constructor
  CMySingleton object2(object);
  // compile fail due to private assignment operator
  object1 = object;

  // ..
  return 0;
}
31 голосов
/ 25 июля 2011

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

Другое использование - принудительное копирование с помощью виртуальной функции. Поскольку конструкторы не могут быть virtual, обычной практикой является предотвращение прямого доступа к конструктору копирования и предоставление метода virtual Base* clone(), который возвращает копию фактического типа времени выполнения, на который указывает указатель. Это предотвращает случайную нарезку, которую может проявить Base b(derived).

Другой пример: простой умный объект указателя, который просто удаляет указатель, данный ему в конструкторе: если он не поддерживает подсчет ссылок или какой-либо другой способ обработки нескольких владельцев и не хочет рисковать неловко непреднамеренная передача права собственности в стиле std::auto_ptr, а затем простое скрытие конструктора копирования дает отличный маленький умный указатель, который быстр и эффективен в ограниченных случаях, когда его можно использовать. Ошибка времени компиляции при попытке скопировать его фактически задала бы программисту «эй - если вы действительно хотите это сделать, измените меня на разделяемый указатель, в противном случае верните обратно!»

4 голосов
/ 25 июля 2011

Очень плохой пример:

class Vehicle : { int wheels; Vehicle(int w) : wheels(w) {} }

class Car : public Vehicle { Engine * engine; public Car(Engine * e) : Vehicle(4), engine(e) }

...

Car c(new Engine());

Car c2(c); // Now both cars share the same engine!

Vehicle v;
v = c; // This doesn't even make any sense; all you have is a Vehicle with 4 wheels but no engine.

Что значит «копировать» автомобиль?(Является ли автомобиль моделью автомобиля или экземпляром автомобиля? Сохраняет ли его копирование регистрацию транспортного средства?)

Что означает присвоение транспортного средства другому?

Еслиоперации бессмысленны (или просто не реализованы), стандартная вещь, которую нужно сделать, - сделать конструктор копирования и оператор присваивания закрытыми, что приведет к ошибке компиляции, если они используются вместо странного поведения.

3 голосов
/ 25 июля 2011

Распространенная причина сделать конструктор копирования и назначение копирования частным - отключить реализацию этих операций по умолчанию.Однако в C ++ 0x есть специальный синтаксис = delete для этой цели.Таким образом, в C ++ 0x создание копии ctor private, похоже, ограничено очень экзотическими случаями.

Копирующие ctor и присваивания являются довольно синтаксическим сахаром;так что такой «частный сахар» кажется симптомом жадности :)

1 голос
/ 25 июля 2011

Даже если содержимое объекта не является указателями или другими ссылками, предотвращение копирования объекта людьми может быть полезным. Возможно, класс содержит много данных, и копирование слишком тяжело для операции.

0 голосов
/ 25 июля 2011

Идиома виртуального конструктора является важным случаем, когда необходим конструктор закрытых или защищенных копий. Проблема возникает в C ++, где вам дается указатель на базовый класс объекта, который на самом деле унаследован от этого базового класса, и вы хотите сделать его копию. Вызов конструктора копирования не вызовет конструктор копирования наследующего класса, но фактически вызовет конструктор копирования базового класса.

Обратите внимание:

class Base {

public:
   Base( const Base & ref ){ std::cout << "Base copy constructor" ; }
};

class Derived : public Base {

public:
   Derived( const Derived & ref ) : Base(ref) { std::cout << "Derived copy constructor"; }
}

Base * obj = new Derived;
Base * obj2 = new Derived(*obj);

Приведенный выше код выдаст результат:

"Base copy constructor"

Это явно не то поведение, которое хотел программист! Программист пытался скопировать объект типа «Производное», но вместо этого получил объект типа «База» !!

Проблема устранена с использованием вышеупомянутой идиомы. Посмотрите на приведенный выше пример, переписанный для использования этой идиомы:

class Base {

public:
  virtual Base * clone () const = 0; //this will need to be implemented by derived class

protected:
   Base( const Base & ref ){ std::cout << "Base copy constructor" ; }
};

class Derived : public Base {

public:
  virtual Base * clone () const {

    //call private copy constructor of class "Derived"
    return static_cast<Base *>( new Derived(*this) );
  }

//private copy constructor:
private:
   Derived( const Derived & ref ) : Base(ref) { std::cout << "Derived copy constructor"; }
}

Base * obj = new Derived;
Base * obj2 = obj->clone();

Приведенный выше код выдаст результат:

"Base copy constructor"
"Derived copy constructor"

Другими словами, объект, который был построен в желаемом типе «Производный», а не в типе «База»!

Как вы можете видеть, в типе Derived конструктор копирования был преднамеренно сделан закрытым, потому что было бы плохо спроектировать API, чтобы дать программистам возможность случайно попытаться вызвать конструктор копирования вручную, а не использовать предоставленный умный интерфейс. по клону (). Иными словами, доступный напрямую вызываемый общедоступный конструктор копирования может привести к тому, что программисты допустят ошибку, упомянутую в части 1. В этом случае в соответствии с передовой практикой конструктор копирования будет скрыт от просмотра и доступен только косвенно с помощью метода "clone ( )».

0 голосов
/ 25 июля 2011

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

...