目录
一、设计一个类,不能被拷贝
1、C++98
2、C++11
二、设计一个类,只能在堆上创建对象
1、将构造设为私有
2、将析构设为私有
三、设计一个类,只能在栈上创建对象
四、设计一个类,不能被继承
1、C++98
2、C++11
五、设计一个类,只能创建一个对象(单例模式)
1、饿汉模式设计单例模式
2、懒汉模式设计单例模式
3、单例对象的释放
4、一种比较简洁但是可能存在线程安全的单例懒汉模式
六、类型转换
1、C语言类型转换
2、C++新增四种强制类型转换
2.1static_cast
2.2reinterpret_cast
2.3const_cast
2.4dynamic_cast
3、RTTI
一、设计一个类,不能被拷贝
1、C++98
class CopyBan
{
private:
CopyBan(const CopyBan& cb);
CopyBan& operator=(const CopyBan& cb);
};
int main()
{
return 0;
}
1、将拷贝构造和赋值运算符重载设置为私有;
2、仅仅私有还不够,这并不能防止类内部就行拷贝。还要对拷贝构造和赋值运算符重载只声明却不实现。
2、C++11
class CopyBan
{
CopyBan(const CopyBan& cb) = delete;
CopyBan& operator=(const CopyBan& cb) = delete;
};
C++11直接使用delete禁用拷贝构造和赋值运算符重载。
二、设计一个类,只能在堆上创建对象
1、将构造设为私有
1、将构造设为私有,防止外部构造,并提供一个CreateObj的函数用于构造堆区对象;
2、但是外部需要对象来调用CreateObj函数来构造对象,所以需要将CreateObj设置为静态函数,无需对象也能调用。
3、外部通过CreateObj函数构造一个对象后,外部可以利用这个对象的指针拷贝构造一个栈区的对象,需要禁用拷贝构造。
2、将析构设为私有
将析构函数设置为私有,栈区对象由于无法析构所以无法创建。堆区对象需要手动调用自己写的清理函数释放。
三、设计一个类,只能在栈上创建对象
1、将构造函数私有;
2、提供一个静态的CreateObj方法用于构造栈区对象;
3、但是无法防止外部构造静态对象。
4、如果想彻底禁止生成静态的对象,需要再禁用拷贝构造。不过这样这个类只能生成临时对象或者引用的对象了,不能修改。
四、设计一个类,不能被继承
1、C++98
class FinalClass
{
static FinalClass CreateObj()
{
return FinalClass();
}
private:
FinalClass()
{}
};
1、禁用构造函数;
2、提供一个静态的CreateObj函数供外部创建父类对象,但是子类会因为构造私有的继承不可见的原因无法构造出父类对象,所以无法继承。
2、C++11
class FinalClass final
{
private:
};
C++11直接使用final关键字
五、设计一个类,只能创建一个对象(单例模式)
一个类只能创建一个对象,即单例模式。该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
1、饿汉模式设计单例模式
class InfoSingleton
{
public:
static InfoSingleton& GetInstance()
{
return _sins;
}
void Insert(string name, int money)
{
_info[name] = money;
}
void Print()
{
for (auto kv : _info)
{
cout << kv.first << ":" << kv.second << endl;
}
}
private:
InfoSingleton()
{}
InfoSingleton(const InfoSingleton&) = delete;
InfoSingleton& operator=(const InfoSingleton&) = delete;
map<string, int> _info;
static InfoSingleton _sins;
};
InfoSingleton InfoSingleton::_sins;
int main()
{
InfoSingleton& s = InfoSingleton::GetInstance();//使用单例对象
s.Insert("小明", 100);
s.Insert("小红", 50);
s.Insert("小绿", 130);
s.Print();
cout << endl;
s.Insert("小绿", 10086);
s.Print();
return 0;
}
饿汉模式:在main函数被加载之前就创建好对象。(全局和静态将在main函数之前被加载)
1、私有构造函数,禁用拷贝构造和赋值运算符重载;
2、在类中声明、外部定义一个静态的对象,用于调用类中私有的构造函数,同时作为单例对象被使用;
3、在类中提供一个获取静态对象的函数GetInstance,为了外部可调用,所以将该函数设置为静态。
饿汉模式的特点:
1、单例对象初始化时,数据太多会导致启动慢;
2、如果多个单例类有初始化的依赖关系,饿汉模式无法控制。例如A和B都是单例类,因为B的启动依赖A,所以需要先初始化A,再初始化B,但是饿汉模式无法控制对象的初始化顺序。
3、饿汉模式创建的对象绝对不会有线程安全问题,因为该模式的对象在main函数之前已经被创建好了,mian函数之前线程都没启动呢。
2、懒汉模式设计单例模式
饿汉模式:第一次获取单例对象的时候创建对象;
1、私有构造函数,禁用拷贝构造和赋值运算符重载;
2、在类中声明、外部定义一个静态的对象指针;
3、在类中提供一个获取静态对象的函数GetInstance,为了外部可调用,所以将该函数设置为静态。
4、它与饿汉的写法区别如图红色标记处。
懒汉模式的特点:
1、对象在main函数之后才会创建;
2、可以主动控制对象的创建时机。
3、创建对象时存在线程安全问题,如果多个线程同时进入红框区域,会可能new多个对象,最后一个创建的对象指针会覆盖之前创建的对象指针,导致内存泄露。
懒汉模式需要加锁解决线程安全问题:
不过红框中new也可能会失败,再套一层try/catch看着太累,可以使用RAII锁解决:
new这里还要try一下异常,main里函数捕获这个异常。(写漏了)
3、单例对象的释放
1、一般单例对象不需要考虑释放,资源会在进程结束时自动释放;
2、释放的写法如下:可以手动清理,将一些资源保存:可手动调用DelInstance进行资源的回收,main函数结束时,操作系统也会自动回收单例对象的资源。
4、一种比较简洁但是可能存在线程安全的单例懒汉模式
class InfoSingleton
{
public:
static InfoSingleton& GetInstance()
{
//静态局部变量是在main函数之后才创建初始化
static InfoSingleton sinst;
return sinst;
}
void Insert(string name, int money)
{
_info[name] = money;
}
void Print()
{
for (auto kv : _info)
{
cout << kv.first << ":" << kv.second << endl;
}
}
private:
InfoSingleton()
{}
InfoSingleton(const InfoSingleton&) = delete;
InfoSingleton& operator=(const InfoSingleton&) = delete;
map<string, int> _info;
};
1、私有构造函数,禁用拷贝构造和赋值运算符重载;
2、通过GetInstance返回静态对象(静态局部变量是在main函数之后才创建初始化)
这种方式构建的单例懒汉模式在C++11发布之前会有线程安全问题,多线程环境下可能会造成静态对象被初始化多次;而C++11规定静态局部变量是线程安全的,可以放心使用。
六、类型转换
1、C语言类型转换
1、隐式类型转换2、强制类型转换。
2、C++新增四种强制类型转换
C++尤其认为隐式类型转化有些情况下可能会出问题:比如数据精度丢失。
2.1static_cast
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
return 0;
}
static_cast适用于相似类型的转换。(可以隐式类型转换的都能用static_cast)
2.2reinterpret_cast
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
// 这里使用static_cast会报错,应该使用reinterpret_cast
//int *p = static_cast<int*>(a);
int* p = reinterpret_cast<int*>(a);
return 0;
}
reinterpret_cast适用于不想关类型之间的转换。(不能隐式类型转换,只能强制类型转换的用reinterpret_cast)
2.3const_cast
const_cast用于删除变量的const属性。需要关注内存可见性问题。
编译器对const变量会有优化,认为const变量不会被改变,编译器在优化代码时可能会将变量放到寄存器或者其他高速缓存中。可以在a初始化时加上volatile关键字,加了volatile关键字后,对变量的读取和写入操作会从内存中进行,而不是从缓存中进行。
2.4dynamic_cast
dynamic_cast用于父类的指针和引用转换为子类对象的指针和引用。(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
class A
{
public:
virtual void f() {}
int _a = 0;
};
class B : public A
{
public:
int _b = 0;
};
void fun(A* ptr)
{
//B* bptr = (B*)ptr;//直接转换是不安全的,父给子存在非法访问隐患
B* bptr = dynamic_cast<B*>(ptr);
if (bptr)//如果转换成功
{
bptr->_a++;
bptr->_b++;
cout << bptr->_a;
cout << bptr->_b;
}
}
int main()
{
A aa;
B bb;
fun(&aa);//转换失败
fun(&bb);//转换成功
return 0;
}
注意:
1. dynamic_cast只能用于父类含有虚函数的类
2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
3、RTTI
RTTI:Run-time Type identifification的简称,即:运行时类型识别。
C++通过以下方式来支持RTTI:
1. typeid运算符
2. dynamic_cast运算符
3. decltype