设置成私有;只声明不定义;默认成员函数后加上delete,表示删除此函数,也就不会被拷贝了。
我们可以把析构函数放在私有,但因为每个对象都被要求显式调用析构函数,所以只能new一个对象,但是释放时又不能delete,所以只能在类内写一个销毁用的函数。
class HeapOnly
{
public:
void Destroy()
{
delete this;
}
private:
~HeapOnly()
{
cout << "~HeapOnly" << endl;
}
int _x;
};
int main()
{
HeapOnly* pho = new HeapOnly;
pho->Destroy();
return 0;
}
另一个思路是构造函数放在私有里,这样即使是new也不行,只能在类内写一个初始化用的函数,在类内new一个。
class HeapOnly
{
public:
HeapOnly* Create(int x = 0)
{
HeapOnly* p = new HeapOnly(x);
return p;
}
private:
HeapOnly(int x = 0)
:_x(x)
{}
int _x;
};
int main()
{
Create(10);
return 0;
}
但是这样会出错,调不动这个Create函数,因为没法实例化对象,也就不能调用类内成员函数。我们可以把Create函数变成static修饰的来解决,但这个方法还是有坑,因为我们可以拷贝到一个新对象里,这个新对象就会创建在栈里,可以加delete来禁止。
class HeapOnly
{
public:
static HeapOnly* Create(int x = 0)
{
HeapOnly* p = new HeapOnly(x);
return p;
}
private:
HeapOnly(int x = 0)
:_x(x)
{}
HeapOnly(const HeapOnly& hp) = delete;
HeapOnly& operator=(const HeapOnly& hp) = delete;
int _x;
};
int main()
{
HeapOnly* p1 = HeapOnly::Create(10);
//HeapOnly p2(*p1);
return 0;
}
把构造函数封掉,那么正常的创建对象方式都没用了,只能借助成员函数来创建,这时候就可以自主选在堆还是栈创建。
class StackOnly
{
public:
static StackOnly Create(int x = 1)
{
return StackOnly(x);
}
private:
StackOnly(int x = 0)
:_x(x)
{}
int _x;
};
int main()
{
StackOnly so1 = StackOnly::Create(10);
return 0;
}
虽然这样可以在栈上创建,但是也无法阻止在堆上创建,比如这样写:static StackOnly so2 = so1,但是我们也不能封拷贝构造,把拷贝构造放在私有里,因为这样Create函数没法传回来对象了。那既然拷贝构造被禁掉了,就加个移动构造,在公有里加个这样的函数
StackOnly(StackOnly&& st)
:_x(st._x)
{
}
但是这样也不行,还可以用move的左值来构造。
这方面确实没办法彻底封死。做到这里也就行了。
构造函数被封掉,也就是给私有化了,也就行了,因为派生类必须调用基类的构造。另外一个办法就是在类后加上final,比如class A final。
设计模式是代码设计的一些总结,通过长期的实践经验而总结出来的代码格式。适配器,迭代器都是设计模式,单例模式也是设计模式,另外还有工厂、观察者模式。
单例模式是设计一个在一个进程中只能创建一个对象的类。在多线程中使用得多。像内存池,每个线程都去访问它,线程池只需要一份即可,内存池就可以设计成单例模式。
单例模式有两类饿汉模式,懒汉模式。先看饿汉模式
class Singleton
{
public:
static Singleton* GetInstance()
{
}
private:
Singleton()//限制类外面随意创建对象
{}
private:
int _n = 0;
vector<string> _v;
};
get函数里要如何限制住只能访问一个对象?我们可以在私有里声明一个静态区的变量,在类外面再去定义它。
class Singleton
{
public:
static Singleton* GetInstance()
{
return _ins;
}
private:
Singleton()//限制类外面随意创建对象
{}
private:
int _n = 0;
vector<string> _v;
static Singleton* _ins;
};
Singleton* Singleton::_ins = new Singleton;
int main()
{
Singleton::GetInstance();
return 0;
}
虽然_ins是静态区的,但也是类内的,可以new去初始化它,这里是一个声明定义分离。
在类外面,只能调用get函数来创建这个类的对象,但还不能直接去操作_v和_ins,我们也没有可使用的对象,所以一切操作就得在类内的public内实现。
class Singleton
{
public:
static Singleton* GetInstance()
{
return _ins;
}
void Add(const string& str)
{
_v.push_back(str);
++_n;
}
void Print()
{
for (auto& e : _v)
{
cout << e << " ";
}
cout << endl;
}
private:
Singleton()//限制类外面随意创建对象
{}
private:
int _n = 0;
vector<string> _v;
static Singleton* _ins;
};
Singleton* Singleton::_ins = new Singleton;
int main()
{
Singleton::GetInstance()->Add("a");
Singleton::GetInstance()->Add("b");
Singleton::GetInstance()->Add("c");
Singleton::GetInstance()->Print();
return 0;
}
加入多线程场景和锁。
#include
#include
#include
#include
#include
#include
using namespace std;
class Singleton
{
public:
static Singleton* GetInstance()
{
return _ins;
}
void Add(const string& str)
{
_mtx.lock();
_v.push_back(str);
++_n;
_mtx.unlock();
}
void Print()
{
_mtx.lock();
for (auto& e : _v)
{
cout << e << endl;
}
_mtx.unlock();
}
private:
Singleton()//限制类外面随意创建对象
{}
private:
mutex _mtx;
int _n = 0;
vector<string> _v;
static Singleton* _ins;
};
Singleton* Singleton::_ins = new Singleton;
int main()
{
//Singleton::GetInstance()->Add("a");
//Singleton::GetInstance()->Add("b");
//Singleton::GetInstance()->Add("c");
//Singleton::GetInstance()->Print();
int n = 10;
//lambda体内不能直接用局部变量,需要放到捕获列表中
thread t1([&]() {
for (size_t i = 0; i < n; ++i)
{
Singleton::GetInstance()->Add("t1线程: " + to_string(rand() + i));
}
});
thread t2([&]() {
for (size_t i = 0; i < n; ++i)
{
Singleton::GetInstance()->Add("t2线程: " + to_string(rand()));
}
});
t1.join();
t2.join();
Singleton::GetInstance()->Print();
return 0;
}
这是一个饿汉模式,在main函数之前就创建对象了,new那一句就是在创建对象,这样对应着饿这个字的意思。
顾名思义,它是需要时才创建对象。
class Singleton
{
public:
static Singleton* GetInstance()
{
if (_ins == nullptr)
{
_ins = new Singleton;
}
return _ins;
}
//......
Singleton* Singleton::_ins = nullptr;
饿汉在你没使用前就创建了,不管用户需不需要。饿汉的问题在于,如果单例对象很大,那么一开始申请就会占用资源,也会降低程序启动速度。
如果两个单例都是饿汉模式,并且有依赖关系,要求单例2先创建,再创建单例1,那么饿汉就无法控制顺序。
饿汉的优点就是简单。
懒汉的缺点在于创建对象时会有线程安全问题,如果t1在new时,还没new完就被切走,t2过来new,然后插入数据,t1回来后会再次new一遍,那么t2之前的数据就被覆盖了,没了,所以需要加锁。由于懒汉在new之前还没有创建对象,所以得在priavte里创建一个静态区的锁,然后在外面给初始化。
class Singleton
{
public:
static Singleton* GetInstance()
{
_imtx.lock();
if (_ins == nullptr)
{
_ins = new Singleton;
}
return _ins;
_imtx.unlock();
}
void Add(const string& str)
{
_vmtx.lock();
_v.push_back(str);
++_n;
_vmtx.unlock();
}
void Print()
{
_vmtx.lock();
for (auto& e : _v)
{
cout << e << endl;
}
_vmtx.unlock();
}
private:
Singleton()//限制类外面随意创建对象
{}
private:
mutex _vmtx;
int _n = 0;
vector<string> _v;
static Singleton* _ins;
static mutex _imtx;
};
Singleton* Singleton::_ins = nullptr;
mutex Singleton::_imtx;//初始化了锁
现在我们写的懒汉模式还有问题吗?每一次获取对象都要加锁解锁,但我们只有第一次加锁解锁,那么把锁放在if代码块里可行吗?也不行,线程不安全。只要是只需要第一次加锁的都可以这样写
static Singleton* GetInstance()
{
//双检查加锁
if (_ins == nullptr)
{
_imtx.lock();
if (_ins == nullptr)//保证线程安全和new一次
{
_ins = new Singleton;
}
_imtx.unlock();
}
return _ins;
}
懒汉模式解决了饿汉的问题,不过相对复杂点,实际生活中大都使用懒汉。
另外一种懒汉设计
class Singleton
{
public:
static Singleton* GetInstance()
{
static Singleton inst;
return &inst;
}
void Add(const string& str)
{
_vmtx.lock();
_v.push_back(str);
++_n;
_vmtx.unlock();
}
void Print()
{
_vmtx.lock();
for (auto& e : _v)
{
cout << e << endl;
}
_vmtx.unlock();
}
~Singleton()
{
;
}
//防拷贝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
private:
Singleton()
{
cout << "Singleton" << endl;
}
mutex _vmtx;
int _n = 0;
vector<string> _v;
};
局部的静态对象,出了作用域就会自动销毁,也不需要内部类。C++11之后这种方式就可以保证线程安全,C++11中局部静态变量是线程安全的,多线程来调用,只会让一个线程进去函数。
单例对象不释放,因为全局都要使用它,只有确定不使用它了就可以释放它,这个接口也得在类内定义。
static void DelInstance()
{
_imtx.lock();
if (_ins)
{
delete _ins;
_ins = nullptr;
}
_imtx.unlock();
}
释放时也有可能特殊要求,析构时要求持久化,也就是里面的数据存到另一个地方去。但是需要每次都调用DelInstance函数,就自动调用析构,完成要求,但如果忘了呢?我们可以定义一个内部类,然后定义一个全局或者静态的内部类对象。
class Singleton
{
public:
static Singleton* GetInstance()
{
//双检查加锁
if (_ins == nullptr)
{
_imtx.lock();
if (_ins == nullptr)
{
_ins = new Singleton;
}
_imtx.unlock();
}
return _ins;
}
void Add(const string& str)
{
_vmtx.lock();
_v.push_back(str);
++_n;
_vmtx.unlock();
}
void Print()
{
_vmtx.lock();
for (auto& e : _v)
{
cout << e << endl;
}
_vmtx.unlock();
}
static void DelInstance()
{
_imtx.lock();
if (_ins)
{
delete _ins;
_ins = nullptr;
}
_imtx.unlock();
}
//单例对象回收
class GC
{
public:
~GC()
{
DelInstance();
}
};
static GC _gc;//定义一个全局或者静态的内部类对象
private:
Singleton()//限制类外面随意创建对象
{}
private:
mutex _vmtx;
int _n = 0;
vector<string> _v;
static Singleton* _ins;
static mutex _imtx;
};
Singleton* Singleton::_ins = nullptr;
mutex Singleton::_imtx;//初始化了锁
Singleton::GC Singleton::_gc;//创建了内部类对象,出了作用域就会自己调用析构
如果忘记了调用释放函数,那么它就可以自动析构,如果没忘记,也没关系,内部类对象析构时会再次调用,然后发现_ins会空,就会什么都不做,解锁。
单例模式还要禁止拷贝构造。有锁的话没法拷贝构造,因为锁不允许拷贝。假设没有锁,我们就要防拷贝。饿汉懒汉都要做。
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
特殊类设计代码
void Test()
{
int i = 1;
//隐式类型转换
double d = i;
printf("%d %.2f\n", i, d);
int* p = &i;
//显式类型转换
int pa = (int)p;
printf("%x, %d\n", p, pa);
}
int main()
{
Test();
return 0;
}
C语言中相关的类型才能进行转换,但会出现隐式类型提升这个坑点。
C++要兼容C语言,还要解决C语言这方面隐式会导致精度丢失,显式会使代码不够清晰的问题,引入了强制类型转换,static_cast,reinterpret_cast,const_cast,dynamic_cast,四者各有用处,且不能互相套着用。
用于非多态类型的转换(静态转换),其实就是以前的隐式类型转换,和隐式一样,不能用于两个不相关的类型进行转换,但解决了精度问题。
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
适用于不相关类型的转换,意思是重新解释。
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
int* p = reinterpret_cast<int*>(a);
用于去掉const属性。
const int a = 2;
int* p = const_cast<int*>(&a);
*p = 3;
cout << a << endl;
cout << *p << endl;
实际上这个代码没有改变a的值,但是*p是3。a确实被修改了,但因为const会被编译器优化,默认识别成不能修改的,比如有的编译器会把代码出现a的地方直接替换成2,所以打印出来还是2,也有可能是把a的值放进寄存器中,每次访问都是访问寄存器,所以改了也没用,要解决这个问题,就是加volatile,禁止编译器优化,每次都去内存中访问a的值。
如果我们强制类型转换,int* p = (int*)&a,其实也可以。只是为了统一规范,C++就用const_cast来做。
C++独有的转换,动态转换,用于基类对象的指针/引用转换为派生类对象的指针或引用。派生类转基类语法本身就支持,而动态转换是解决基类转派生类的。但是无论如何,类对象之间是不能转换的。
class A
{
public:
virtual void f(){};
};
class B : public A
{};
void fun(A* pa)
{
B* pb1 = static_cast<B*>(pa);//(B*)(pa)也行
B* pb2 = dynamic_cast<B*>(pa);
cout << "pb1:" << pb1 << endl;
cout << "pb2:" << pb2 << endl;
}
int main()
{
A a;
B b;
fun(&a);
fun(&b);
return 0;
}
父类指针如果指向子类对象,那么它转子类指针是安全的,用static_cast都可,但如果本身指向父类对象,那么转子类指针就不安全,因为访问子类成员时可能会越界。动态转换如果检测指向父类对象就会转换失败,如果指向子类对象就会转换成功。
可以这样写来查看结果
class A
{
public:
virtual void f(){};
};
class B : public A
{};
void fun(A* pa, const string& s)
{
cout << "pa指向" << s << endl;
B* pb1 = (B*)pa;
B* pb2 = dynamic_cast<B*>(pa);
cout << "[强制转换]pb1:" << pb1 << endl;
cout << "[dynamic_cast转换]pb2:" << pb2 << endl;
}
int main()
{
A a;
B b;
fun(&a, "父类对象");
fun(&b, "子类对象");
return 0;
}
运行时类型识别。RAII是利用对象来管理资源。RTTI通过typeid,dynamic_cast,decltype来支持。
//用上一段代码
int main()
{
A a;
B b;
fun(&a, "父类对象");
fun(&b, "子类对象");
cout << typeid(a).name() << endl;//对象类型的字符串
decltype(a) aa;//获取类型当作aa的类型,用来获取一些不好直接写出来的类型
function<bool(int,int)> gt = [](int x, int y) {return x > y; };
set<int, decltype(gt)> s;
return 0;
}
类型转换代码
结束。