在c ++中使用私有拷贝构造函数有什么用处

时间:2011-07-25 01:36:20

标签: c++ copy-constructor assignment-operator

为什么人们定义私有拷贝构造函数?

何时将复制构造函数和赋值运算符设为私有设计?

如果类中没有成员是唯一对象(如文件名)的指针或句柄,那么在其他情况下,私有拷贝构造函数是个好主意吗?

同样的问题适用于赋值运算符。鉴于大多数C ++围绕复制对象并通过引用传递,是否有任何涉及私有拷贝构造函数的好设计?

7 个答案:

答案 0 :(得分:34)

一个用例是单例模式,其中只能有一个类的实例。在这种情况下,您需要使构造函数和赋值operator = private,以便无法创建多个对象。创建对象的唯一方法是通过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;
}

答案 1 :(得分:30)

某些对象表示不能或不应复制的特定实体。例如,您可以阻止复制表示应用程序使用的日志文件的对象,这与期望代码的所有部分将使用单个日志文件相对应。使用意外或不正确复制的对象可能导致日志中出现无序内容,当前日志大小的不准确记录,多次尝试(某些失败)“滚动”到新的日志文件名或重命名现有文件名。

另一个用途是通过虚拟功能强制执行复制。由于构造函数不能是virtual,因此通常的做法是阻止直接访问复制构造函数并提供virtual Base* clone()方法,该方法返回指针指向的实际运行时类型的副本。这可以防止Base b(derived)出现的意外切片。

另一个例子:一个死简单的智能指针对象,它只是删除它在构造函数中给出的指针:如果它不支持引用计数或其他一些处理多个所有者的方式,并且不希望风险尴尬无意的std::auto_ptr样式的所有权转移,然后简单地隐藏复制构造函数给出了一个很好的小智能指针,它对于可用的有限情况快速有效。关于尝试复制它的编译时错误会有效地要求程序员“嘿 - 如果你真的想这样做会把我改成共享指针,否则就退后了!”。

答案 2 :(得分:4)

一个非常糟糕的例子:

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 :(得分:3)

将复制构造函数和复制赋值设置为私有的常见原因是禁用这些操作的默认实现。 但是,在C ++ 0x中,出于此目的,存在特殊的语法= delete 。 因此,在C ++ 0x中,复制ctor private似乎被重新归结为非常奇特的情况。

复制ctors和作业是相当的语法糖;所以这种“私人糖”似乎是贪婪的症状:)

答案 4 :(得分:1)

即使对象的内容不是指针或其他引用,阻止人们复制对象仍然有用。也许该类包含大量数据,并且复制过于重要的操作。

答案 5 :(得分:0)

您可能希望使用复制构造函数实现该类的某些方法,但不要将其暴露在类之外。那么你把它变成私有的。像任何其他方法一样。

答案 6 :(得分:0)

virtual constructor idiom”是需要私有或受保护的复制构造函数的重要案例。在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"

这显然不是程序员想要的行为!程序员试图复制“Derived”类型的对象,而是找回“Base”类型的对象!!

使用上述习语纠正了这个问题。观察上面写的例子,重写以使用这个成语:

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”的对象,而不是“Base”类型的对象!

正如您所看到的,在Derived类型中,复制构造函数是故意设置为私有的,因为给程序员提供能够意外地尝试手动调用复制构造函数而不是使用提供的聪明接口的API设计是不好的通过clone()。换句话说,可直接调用的公共拷贝构造函数可能会导致程序员犯下第1部分中提到的错误。在这种情况下,最佳实践是隐藏复制构造函数,并且只能通过使用“clone”方法间接访问)”。