不允许赋值和传递值

Disallowing assignment and passing by value

本文关键字:赋值 不允许      更新时间:2023-10-16

据我所知,我可以通过定义私有复制构造函数和赋值运算符来"禁用"复制和赋值给我的对象:

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

但是这个有什么用呢
这被认为是一种糟糕的做法吗?

如果你能描述一下这种情况,我将不胜感激,在这种情况下,以这种方式"禁用"赋值和复制构造函数是合理/有用的。

当复制对象没有意义时,它很有用。这绝对不是一种坏做法。

例如,如果您有一个表示网络连接的类,那么复制该对象是没有意义的。另一种情况是,如果你有一个类在多人游戏中代表一个玩家,那么你可能希望一个类是不可复制的。这两个类都代表了在现实世界中无法复制的东西,或者复制起来没有意义的东西(一个人,一个连接)。

此外,如果您正在尝试实现Singleton,那么使对象不可复制是标准过程。

一般来说,任何管理资源的类都应该是不可复制的,或者具有专门的复制语义。反之亦然:任何不可复制或需要专门复制语义的类都在管理资源。在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已经释放了相同的资源,完成了双重删除——这是一种臭名昭著的未定义行为场景。此外,在C++中,复制对象非常容易,甚至不会注意到:一个函数按值获取参数,按值返回参数,或者按引用获取参数,然后调用另一个函数,该函数本身按值获取(或返回)参数。。。更容易假设的东西将尝试被复制。

所有这些都表明,当类的存在理由是管理资源时,您应该立即知道需要处理复制。你应该决定

  • 您支持复制,而您决定复制意味着什么:安全共享资源,执行底层资源的深度复制,这样就不会有任何共享,或者将这两种方法结合起来,如写时复制或延迟复制。无论选择何种路径,都需要提供专门的复制构造函数和复制赋值运算符
  • 或者您不支持对资源进行任何类型的复制,在这种情况下,您将禁用复制构造函数和复制赋值运算符

我想说的是,资源管理是禁用复制或提供专门复制语义的唯一情况。这只是《三条规则》的另一个视角。

这是一种非常常见的做法。有很多不适合复制的例子。

假设您的对象表示一个开放的服务器端套接字(即传入的网络连接);复制该对象的语义是什么?

当您只允许在检查后创建对象的实例时,就像在singleton的情况下一样,u需要私有构造函数。当构造函数被调用时,对象实例将被调用,然后检查是否已经有另一个实例是没有意义的。因此,我们所做的是从main调用类的成员函数,并在该成员函数内部检查内存中是否已经有另一个实例。如果不是,则调用构造函数。否则中止。检查singleton类或其他受保护的类,在这些类中,对象的数据必须保持安全,并且不应允许复制。

还可以检查一下:C++中的Singleton类

当你试图实现一个Singleton模式时,使用私有构造函数是完全可以接受的,因为它只在自己内部实例化,而不在其他地方实例化。一旦被调用,构造函数就不能被撤销。因此,只有在检查单例条件是否满足后,才会调用构造函数。