C++特殊类设计+类型转换

特殊类设计

1. 请设计一个类,只能在堆上创建对象

实现方式:

  1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
  2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
class HeapOnly    
{     
public:     
    static HeapOnly* CreateObject()  
    {      
        return new HeapOnly;    
    }
private:    
    HeapOnly() {}
    
    // C++98
    // 声明成私有
    HeapOnly(const HeapOnly&);
    
    // or 
    // C++11    
    HeapOnly(const HeapOnly&) = delete;
};

2. 请设计一个类,只能在栈上创建对象

方法一:同上将构造函数私有化,然后设计静态方法创建对象返回即可。

class StackOnly    
{    
public:    
    static StackOnly CreateObject()  
    {      
        return StackOnly();   
    }
private:
    StackOnly()  {}
};

方法二:屏蔽new
因为new在底层调用void* operator new(size_t size)函数,只需将该函数屏蔽掉即可。
注意:也要防止定位new

class StackOnly    
{    
public:    
    StackOnly()  {}
private:    
    void* operator new(size_t size);
    void operator delete(void* p);
}; 

3. 请设计一个类,不能被拷贝

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可

  • C++98
    将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
class CopyBan
{
    // ...
    
private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
    //...
};

原因:

  1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
  2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
  • C++11
    C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数

4. 请设计一个类,不能被继承

  • C++98方式
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:
    static NonInherit GetInstance()
    {
        return NonInherit();
    }
private:
    NonInherit()
    {}
};
  • C++11方法
    final关键字,final修饰类,表示该类不能被继承
class A  final
{
    // ....
};

5. 请设计一个类,只能创建一个对象(单例模式)

设计模式:

设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性

单例模式:

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

  • 饿汉模式
    不管你将来用不用,程序启动时就创建一个唯一的实例对象。
// 饿汉模式
// 优点:简单
// 缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		return &m_instance;
	}

private:
	// 构造函数私有
	Singleton(){};

	// C++98 防拷贝
	Singleton(Singleton const&);
	Singleton& operator=(Singleton const&);

	// or

	// C++11
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

	static Singleton m_instance;
};

Singleton Singleton::m_instance;  // 在程序入口之前就完成单例对象的初始化
  • 懒汉模式
    如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
// 懒汉
// 优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
// 缺点:复杂
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;

class Singleton
{
public:
	static Singleton* GetInstance() {
		// 注意这里一定要使用Double-Check的方式加锁,才能保证效率和线程安全
		if (nullptr == m_pInstance) {
			m_mtx.lock();
			if (nullptr == m_pInstance) {
				m_pInstance = new Singleton();
			}
			m_mtx.unlock();
		}
		return m_pInstance;
	}

	// 实现一个内嵌垃圾回收类    
	class CGarbo {
	public:
		~CGarbo(){
			if (Singleton::m_pInstance)
				delete Singleton::m_pInstance;
		}
	};

	// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
	static CGarbo Garbo;

private:
	// 构造函数私有
	Singleton(){};

	// 防拷贝
	Singleton(Singleton const&);
	Singleton& operator=(Singleton const&);

	static Singleton* m_pInstance; // 单例对象指针
	static mutex m_mtx;            //互斥锁
};

Singleton* Singleton::m_pInstance = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::m_mtx;

void func(int n)
{
	cout << Singleton::GetInstance() << endl;

}
// 多线程环境下演示上面GetInstance()加锁和不加锁的区别。
int main()
{
	thread t1(func, 10);
	thread t2(func, 10);

	t1.join();
	t2.join();

	cout << Singleton::GetInstance() << endl;
	cout << Singleton::GetInstance() << endl;
}

C++的类型转换

1. C语言中的类型转换

在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。

  1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
  2. 显式类型转化:需要用户自己处理
void Test ()
{
     int i = 1;
     // 隐式类型转换
     double d = i;
     printf("%d, %.2f\n" , i, d);
 
     int* p = &i;
     // 显示的强制类型转换
     int address = (int) p;
 
     printf("%x, %d\n" , p, address);
}

缺陷: 转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换

2. 为什么C++需要四种类型转换

C风格的转换格式很简单,但是有不少缺点的:

  1. 隐式类型转化有些情况下可能会出问题:比如数据精度丢失
  2. 显式类型转换将所有情况混合在一起,代码不够清晰

因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。

3. C++强制类型转换

标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast、reinterpret_cast、const_cast、dynamic_cast

3.1 static_cast

static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换

int main()
{
    double d = 12.34;
    int a = static_cast<int>(d);
    cout<<a<<endl;
    return0;
}

3.2 reinterpret_cast

reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型

typedef void (* FUNC)();
 int DoSomething (int i)
 {
     cout<<"DoSomething" <<endl;
     return 0;
 }
 
 void Test ()
 {
     //
     // reinterpret_cast可以编译器以FUNC的定义方式去看待DoSomething函数
     // 所以非常的BUG,下面转换函数指针的代码是不可移植的,所以不建议这样用
     // C++不保证所有的函数指针都被一样的使用,所以这样用有时会产生不确定的结果
     //
     FUNC f = reinterpret_cast< FUNC>(DoSomething );
     f();
 }

3.3 const_cast

const_cast最常用的用途就是删除变量的const属性,方便赋值

void Test ()
 {
     const int a = 2;
     int* p = const_cast< int*>(&a );
     *p = 3;
 
     cout<<a <<endl;
 }

3.4 dynamic_cast

dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则) 向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)

注意:
1. dynamic_cast只能用于含有虚函数的类
2. dynamic_cast会先检查是否能转换成功,能成功则转
换,不能则返回0

class A
 {
 public :
     virtual void f(){}
 };
 
 class B : public A
 {};
 
 void fun (A* pa)
 {
   // dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回
   B* pb1 = static_cast<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;
 }

3.5 explicit

explicit关键字阻止经过转换构造函数进行的隐式转换的发生

class A
 {
 public :
     explicit A (int a)
    {
         cout<<"A(int a)" <<endl;
    }
 
     A(const A& a)
    {
         cout<<"A(const A& a)" <<endl;
    }
 private :
     int _a ;
 };
 
 int main ()
 {
     A a1 (1);
 
     // 隐式转换-> A tmp(1); A a2(tmp);
     A a2 = 1;
 }

猜你喜欢

转载自blog.csdn.net/weixin_44826356/article/details/105826115
今日推荐