📜  C++ 单例防止复制 - C++ (1)

📅  最后修改于: 2023-12-03 14:39:54.922000             🧑  作者: Mango

C++ 单例防止复制

在编程中,单例模式是一种经常使用的设计模式。它允许您创建只有一个实例的类,并且提供全局访问点以使代码更加简洁和易于管理。然而,一些问题也会伴随单例模式而来,例如复制对象时可能会引起不可预测的行为。因此,在本文中,我们将重点关注如何防止单例类的复制。

需要防止复制的原因

当我们使用单例模式时,我们不希望有多个实例存在。这就是单例模式的目的。然而,在某些情况下,如复制实例时,我们可能不会按照预期方式进行。下面是一个例子。

#include <iostream>

class Singleton {
 public:
  static Singleton* GetInstance() {
    static Singleton instance;
    return &instance;
  }

  // 需要防止的复制函数
  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;

  void SayHello() {
    std::cout << "Hello from Singleton!" << std::endl;
  }
};

int main() {
  Singleton* s1 = Singleton::GetInstance();
  s1->SayHello();

  Singleton* s2 = Singleton::GetInstance();

  // 尝试复制
  Singleton s3(*s2);

  return 0;
}

在上面的代码中, Singleton 类定义了一个只有一个实例的类。我们保证了 Singleton 的唯一性,使得我们无法创建其他的实例。 然而,我们没有防止对单例实例的复制。在上面的代码中,我们通过调用 GetInstance 方法来获取单例实例,并将其分别赋给s1s2。然后,我们尝试将 s2 复制到 s3 中。

尽管 GetInstance 方法返回的是一个指向同一实例的指针,但是现在通过 s2s3,两个指针都引用了堆上的单例实例。 这就会导致一个问题:由于实例共享相同的数据,因此在对 s3 进行更改时,s2 也会受到影响。这很可能会导致不可预测的错误。

防止复制的两种方法
方法一:删除复制函数

一个有效的解决方案是直接删除复制函数,以防止对单例类的复制。如下所示:

class Singleton {
 public:
  // ...
  
  // 删除复制函数
  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;

  // ...
};

使用上面的代码,我们可以有效地防止变量及其副本之间的数据共享。 但是,我们需要确保我们始终只有一个实例。

方法二:只允许复制同一实例

另一个值得考虑的解决方案是只允许从同一实例中复制。为了使这种方法起作用,我们需要在类中添加一个私有构造函数和一个友元类或函数。只有好友类和函数才能够创建新实例并将数据从单例复制到新实例中。这就可以防止我们从不同的单例实例复制数据。

class Singleton {
  friend class SingletonCopier;
 public:
  static Singleton* GetInstance() {
    static Singleton instance;
    return &instance;
  }

  // ...

 private:
  // 私有同步构造函数
  Singleton() = default;
  Singleton(const Singleton&) = default;
  Singleton& operator=(const Singleton&) = default;
};

// 好友类,复制单例
class SingletonCopier {
 public:
  SingletonCopier() {
    Singleton* singleton = Singleton::GetInstance();
    new (this) Singleton(*singleton);
  }
};

使用上面的代码,我们可以在需要复制单例实例时,由 SingletonCopier 类来完成复制。由于 SingletonCopier 类是 Singleton 类的友元,因此它可以访问 Singleton 类的所有成员。在复制实例时,必须确保在使用新实例之前删除旧实例。

int main() {
  Singleton* s1 = Singleton::GetInstance();
  s1->SayHello();

  SingletonCopier copier;

  Singleton* s2 = Singleton::GetInstance();
  s2->SayHello();

  // 确保销毁旧实例
  delete s1;

  // ...

  return 0;
}

上面的代码中,当我们使用 SingletonCopier 类来复制单例实例并创建一个新实例时,必须确保在使用新实例之前删除旧实例。这确保我们在创建新实例时没有旧实例可用。

结论

以上是在单例类中防止复制的两种方法:一种是删除复制函数,另一种是只允许从同一个实例中复制。当我们使用单例模式时,我们需要注意避免不必要的复制。如果您的单例类需要用到复制,请务必考虑选择适合您的具体代码的方法。