拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
对此,C++98的实现方式是==将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有。==如下:
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
这样做的原因是:
- 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不
能禁止拷贝了- 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写
反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
但是,这样设计也有一些弊端。
首先,类的用户可能不会意识到该类禁止拷贝操作,从而可能会在使用时出现错误。
其次,由于编译器自动生成的拷贝构造函数和拷贝赋值运算符被禁止使用,所以如果需要在代码中执行拷贝操作,就必须手动编写对应的移动构造函数和移动赋值运算符,这可能增加代码的复杂性。
C++11及其后续版本引入了更好的解决方案,即通过将拷贝构造函数和拷贝赋值运算符声明为deleted,来明确禁止拷贝操作。此外,C++11还引入了移动构造函数和移动赋值运算符,使得对特殊类的处理更加简便和安全。如下:
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
};
这样,当禁用拷贝构造函数和拷贝赋值运算符时,编译器会在对应的调用上产生一个编译错误。这样做可以有效阻止对该类的对象进行拷贝操作。
为了实现这个要求,首先要将构造函数放在私有成员中,防止外界随便创建对象;其次,再增加一个公有成员函数,用来专门在堆上创建一个对象并返回。如下:
class OnlyOnHeap
{
public:
OnlyOnHeap* CreateObj()
{
return new OnlyOnHeap;
}
private:
OnlyOnHeap()
{}
};
但是这样,在外部调用CreateObj时,却是行不通的。
原因在于,要想调用公有成员函数,首先要有一个对象。但是要创建一个对象,目前的情况来看只能通过这个函数调用。彼此矛盾。
所以,为了解决这个问题,可以将成员函数变为静态的,这样它就没有this指针了,也就可以在外部直接调用了。
如下:
class OnlyOnHeap
{
public:
static OnlyOnHeap* CreateObj()
{
return new OnlyOnHeap;
}
private:
OnlyOnHeap()
{}
};
int main()
{
OnlyOnHeap* tmp = OnlyOnHeap::CreateObj();
return 0;
}
但是这样还是存在漏洞,当用户在外部按照上述方法创建了一个在堆上的对象之后,可以直接对其解引用,然后会自动调用它自动生成的拷贝构造,这样就会创造出一个在栈上的对象。
所以,为了避免上述问题,还要禁止使用它的拷贝构造函数,如下:
class OnlyOnHeap
{
public:
static OnlyOnHeap* CreateObj()
{
return new OnlyOnHeap;
}
private:
OnlyOnHeap()
{}
OnlyOnHeap(const OnlyOnHeap&) = delete;
};
还有另外一种方法:把析构函数设为私有,把构造函数设为公有,如下:
class OnlyOnHeap
{
public:
OnlyOnHeap()
{}
private:
~OnlyOnHeap()
{}
OnlyOnHeap(const OnlyOnHeap&) = delete;
};
int main()
{
OnlyOnHeap* tmp = new OnlyOnHeap;
return 0;
}
但是这种方法,导致不能在外部使用delete释放对象,因为不能在外部调用析构函数。
所有,需要增加一个成员函数,用它来间接调用析构函数,如下:
class OnlyOnHeap
{
public:
OnlyOnHeap()
{}
void Destroy()
{
this->~OnlyOnHeap();
}
private:
~OnlyOnHeap()
{}
OnlyOnHeap(const OnlyOnHeap&) = delete;
};
int main()
{
OnlyOnHeap* tmp = new OnlyOnHeap;
OnlyOnHeap::Destroy();
return 0;
}
同上将构造函数私有化,然后设计静态方法创建对象返回即可,如下:
class OnlyOnStack
{
public:
static OnlyOnStack CreateObj()
{
return OnlyOnStack();
}
private:
OnlyOnStack(){}
};
int main()
{
OnlyOnStack obj = OnlyOnStack::CreateObj();
return 0;
}
但是这种方法还是可以创建静态的对象的,所以这个要求的实现还是有缺陷的,做不到百分百的符合要求。
在C++98中,可以通过将类的构造函数声明为私有来防止其他类继承该类。由于派生类需要调用基类的构造函数来完成对象的构造,而私有构造函数无法在派生类中直接访问,因此无法创建继承自该类的对象。如下:
class CannotBeInherited
{
private:
CannotBeInherited() {} // 私有构造函数
friend class SomeOtherClass; // 允许某些类访问私有构造函数
};
在C++11中,可以使用 final关键字来声明一个类,表示该类不能被继承。如下:
class CannotBeInherited final
{
// 类定义
};
使用 final关键字修饰类后,任何试图从此类派生的尝试都会导致编译错误。
需要注意的是,在C++11中还可以通过将基类的析构函数声明为虚函数,并将其设为纯虚函数(= 0),从而使得该类成为一个抽象类,无法直接实例化或继承。这种方式一般适用于需要通过派生类来实现多态性和覆盖虚函数的情况。如下:
class CannotBeInheritedAbstract
{
public:
virtual ~CannotBeInheritedAbstract() = 0;
};
CannotBeInheritedAbstract::~CannotBeInheritedAbstract() {}
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
它有两种实现模式:饿汉模式和懒汉模式。
饿汉模式指的是一开始就创建对象。
既然要求只能有一个全局的对象,就毫无疑问要把构造函数设为私有
然后设置一个静态私有的对象,设置一个静态公有的成员函数,以便间接对对象进行操作。如下:
class InfoSingleton
{
public:
static InfoSingleton& GetInstance()
{
return _sins;
}
void Insert(string name, int n)
{
_info[name] = n;
}
void Print() const
{
for (auto e : _info)
{
cout << e.first << ": " << e.second << endl;
}
}
private:
InfoSingleton() {}
map<string, int> _info;
private:
static InfoSingleton _sins;
};
InfoSingleton InfoSingleton::_sins;
int main()
{
InfoSingleton::GetInstance().Insert("张三", 100);
InfoSingleton& info = InfoSingleton::GetInstance();
info.Insert("李四", 200);
info.Print();
return 0;
}
结果如下:
但是,这并不是真正的单例,因为上述代码中还可以使用拷贝构造(默认生成)。所以,还需要用其他方法使拷贝构造禁止使用。如下:
class InfoSingleton
{
public:
static InfoSingleton& GetInstance()
{
return _sins;
}
void Insert(string name, int n)
{
_info[name] = n;
}
void Print() const
{
for (auto e : _info)
{
cout << e.first << ": " << e.second << endl;
}
}
private:
InfoSingleton() {}
map<string, int> _info;
InfoSingleton(const InfoSingleton& info) = delete;
InfoSingleton& operator=(const InfoSingleton& info) = delete;
private:
static InfoSingleton _sins;
};
饿汉模式的缺点:
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
跟饿汉模式不同在于,它不是一开始就创建对象,而是在第一次获取单例对象时创建对象。在main函数之后才会创建,不会影响启动速度。如下:
class InfoSingleton
{
public:
static InfoSingleton& GetInstance()
{
//第一次获取单例对象的时候创建对象
if (_sins == nullptr)
{
_sins = new InfoSingleton;
}
return *_sins;
}
void Insert(string name, int n)
{
_info[name] = n;
}
void Print() const
{
for (auto e : _info)
{
cout << e.first << ": " << e.second << endl;
}
}
private:
InfoSingleton() {}
map<string, int> _info;
InfoSingleton(const InfoSingleton& info) = delete;
InfoSingleton& operator=(const InfoSingleton& info) = delete;
private:
static InfoSingleton* _sins;
};
但是上面的代码还是存在线程安全的问题,当多个县城一起调用GetInstance时,会有风险。所以,应该对上面的代码加锁保证安全。如下:
class InfoSingleton
{
public:
static InfoSingleton& GetInstance()
{//双检查加锁,针对第一次创建对象,避免每次都加锁
if (_sins == nullptr)
{
//第一次获取单例对象的时候创建对象
std::lock_guard<mutex> lock(_smtx);
if (_sins == nullptr)
{
_sins = new InfoSingleton;
}
}
return *_sins;
}
// 实现一个内嵌垃圾回收类
class CGarbo
{
public:
CGarbo()
{
if (Singleton::m_pInstance)
delete Singleton::m_pInstance;
}
};
// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
static CGarbo Garbo;
void Insert(string name, int n)
{
_info[name] = n;
}
void Print() const
{
for (auto e : _info)
{
cout << e.first << ": " << e.second << endl;
}
}
private:
InfoSingleton() {}
map<string, int> _info;
InfoSingleton(const InfoSingleton& info) = delete;
InfoSingleton& operator=(const InfoSingleton& info) = delete;
private:
static InfoSingleton* _sins;
static mutex _smtx;
};
InfoSingleton* InfoSingleton::_sins= nullptr;
InfoSingleton::CGarbo Garbo;
mutex InfoSingleton::_smtx;
本篇完!青山不改,绿水长流!