45 C++类型转换和特殊类设计,单例模型详解
目录
一. C/C++类型转换
1.1 C语言类型转换
C语言并没有提供关键字用于类型转换,只有显示类型转换和隐式类型转换。
显示类型转换:也就是我们常说的强制类型转换
#include <iostream>
void testC()
{
// 显示类型转换
int a = 123;
char c = (char)a;
printf("%d %c\n", a, c);
}
int main()
{
testC();
}

隐式类型转换:编译器自行决定的转换
#include <iostream>
void testC()
{
// 隐式类型转换
double num = 123.456;
int ans = num;
printf("num:%d,ans:%d\n", num, ans);
}
int main()
{
testC();
}

1.2 C++类型转换
C语言的强制类型转换过于危险,所以C++在支持C语言类型的转换的前提下,还提供了更为安全和规范的类型转换。
a static_cast
static_cast类似于隐式类型转换,常用于相近类型的转换。
1 可以用于基本类型的转换
2 可以用于将void*转换为其他指针
3 可以支持类的向上转换,即派生类转化为基类
class father
{
int a;
};
class son : public father
{
int b;
};
void test_static_cast()
{
// 1.基本类型的转化
double num = 123.456;
int a = static_cast<int>(num);
// 2.将void*转化为其他指针
void *p1 = #
int *p2 = static_cast<int *>(p1);
// 3.支持类的向上转换
son s;
father f = static_cast<father>(s);
}
注意:static_cast不可用于两个无关类之间的转换
b dynamic_cast
C++提供的动态类型转换,为了支持动态多态。用于父子类之间的向下转换,该转换有一些自己的特点:
1 依赖运行时检查RTTI,用于转换类型时候的安全检查
2 用于多态(必须要有虚函数),注意是向下转化
3 多态由指针实现/引用实现,指针转换失败返回nullptr,引用转换失败返回std::bad_cast
这有什么用呢?比如当我们的派生类有自己的特有函数时候,可以通过将基类指针/引用强制转化为派生类去执行对应的方法。
class father
{
virtual void f() {};
};
class son : public father
{
virtual void f() override {};
public:
void test()
{
std::cout << "void test()" << std::endl;
}
};
void test_dynamic_cast(father *pf)
{
// 用于将基类指针/引用强制转化为派生类指针/引用?
if (son *ps = dynamic_cast<son *>(pf))
{
// 执行son特有的函数
ps->test();
}
else
{
std::cout << "转换失败,无法转换" << std::endl;
}
}
int main()
{
father *pf = new father();
father *ps = new son();
test_dynamic_cast(pf);
test_dynamic_cast(ps);
}
可以退出,传入pf时候,无法转换大于else分支,传入ps可以转换,大于ps的test。程序运行结果如下:

dynamic_cast的原理是?:在动态多态中,每一个对象都有一个虚表指针指向虚表。
c const_cast 与 volatile
这个很容易理解,就是去除一个const变量的const属性
void test_const_cast()
{
const int a = 1;
// int &b = a; 会报错
int &b = const_cast<int &>(a);
b = 2;
std::cout << "a:" << a << " b:" << b << std::endl;
}
int main()
{
test_const_cast();
}
运行结果如下:

为何a和b的值不一样?我们打印地址看一下
void test_const_cast()
{
const int a = 1;
// int &b = a; 会报错
int &b = const_cast<int &>(a);
b = 2;
std::cout << "a:" << a << " b:" << b << std::endl;
printf("a addr:%p\nb addr:%p\n", &a, &b);
}

可以看到,a和b的地址是一样的:上面的原因是编译器优化造成的,由于a是const值,编译器直接将 a的值 1放到寄存器中,最后读取的当然是 1。
这里使用volatile关键字让让编译器保持a的内存可见性,即每一次都去内存中读数据
void test_const_cast()
{
volatile const int a = 1;
// int &b = a; 会报错
int &b = const_cast<int &>(a);
b = 2;
std::cout << "a:" << a << " b:" << b << std::endl;
printf("a addr:%p\nb addr:%p\n", &a, &b);
}

d reinterpret_cast
用于不相近类型之间的转换,这个是直接按照变量的位来重新解释变量。比如
char c = 'a';
如果按照字符类型解释就是 'a',按照整形解释就是97。是最接近C语言的强制类型转换的。注意这个转换是比较危险且没有检查的!
二. 特殊类设计
2.1 只能在栈定义变量的类
设计一个类,只能在栈上定义:
分析:在栈上定义,那么不能malloc或者new。或者将构造函数私有,定义新函数获取变量
这里采用第二种方法:
class StackOnly
{
public:
StackOnly GetObject()
{
return StackOnly();
}
private:
StackOnly(){}
};
2.2 只能在堆定义变量的类
定义一个类,只能在堆上定义。
同理,我们将构造函数私有,然后通过一个函数返回对象的指针
// 只能在堆中定义
class headOnly
{
public:
headOnly *getObjectPtr()
{
return new headOnly();
}
private:
headOnly() {}
};
2.3 单例模式⭐
单例模式就是一个类只能实例化一个对象。如何实现呢?
功能分析:
只能有一个实例,那么说我们首先要将拷贝构造私有化并禁止用户去调用拷贝/赋值等函数。同时需要提供一个接口给用户用于获取唯一的对象
这给对象是唯一的,只有通过全局对象或者静态对象。这里只要在类中定义一个静态对象即可
a 饿汉模式
我们直接在类中创建一个静态对象并完成这个对象的初始化。
// 饿汉模式
class singleton
{
private:
// 唯一静态对象
static singleton *_pinst;
};
// 类外提前初始化
singleton *singleton::_pinst = new singleton();
同时需要私有构造,禁用拷贝/赋值等函数
// 饿汉模式
class singleton
{
private:
singleton() {}
singleton(singleton &) = delete;
singleton &operator=(singleton &) = delete;
private:
// 唯一静态对象
static singleton *_pinst;
};
// 类外提前初始化
singleton *singleton::_pinst = new singleton();
最后我们为用户提供一个接口用于获取唯一静态对象即可!
// 饿汉模式
class singleton
{
public:
static singleton *getObject()
{
return _pinst;
}
private:
singleton() {}
singleton(singleton &) = delete;
singleton &operator=(singleton &) = delete;
private:
// 唯一静态对象
static singleton *_pinst;
};
// 类外提前初始化
singleton *singleton::_pinst = new singleton();
就此,一个单例模式就实现了。是不是比较简单!?
有什么不好的地方呢?如果一个类比较大,初始化需要时间较多,这样并不能延迟加载。有没有一种方式在需要的时候再去初始化呢?
b 懒汉模式
懒汉模式也是一种单例模式,并不是定义时候就初始化,而是调用时候才去初始化,具有延迟加载的功能。
我们对上面饿汉模式的代码进行简易修改即可获得懒汉模式的代码
// 懒汉模式
class lazySingleton
{
public:
static lazySingleton *getObject()
{
_pinst = new lazySingleton();
return _pinst;
}
private:
lazySingleton() {}
lazySingleton(lazySingleton &) = delete;
lazySingleton &operator=(lazySingleton &) = delete;
private:
// 唯一静态对象
static lazySingleton *_pinst;
};
这样行不行?不可以,因为对象是唯一的,这样每一次返回的都是新对象指针
// 懒汉模式
class lazySingleton
{
public:
static lazySingleton *getObject()
{
// 只有为空的时候才进行初始化
if (_pinst == nullptr)
_pinst = new lazySingleton();
return _pinst;
}
private:
lazySingleton() {}
lazySingleton(lazySingleton &) = delete;
lazySingleton &operator=(lazySingleton &) = delete;
private:
// 唯一静态对象
static lazySingleton *_pinst;
};
这样貌似可以,不过还需要考虑线程安全问题,如果有多个线程去调用这个函数的话就会有线程安全问题
// 懒汉模式
std::mutex lock;
class lazySingleton
{
public:
static lazySingleton *getObject()
{
// 只有为空的时候才进行初始化
{
std::unique_lock<std::mutex> ul(lock);
if (_pinst == nullptr)
_pinst = new lazySingleton();
}
return _pinst;
}
private:
lazySingleton() {}
lazySingleton(lazySingleton &) = delete;
lazySingleton &operator=(lazySingleton &) = delete;
private:
// 唯一静态对象
static lazySingleton *_pinst;
};
这样就能保证线程安全了!不过还能继续优化,这里每一个进入get函数都会加锁保护,这样占用较多的性能。
// 懒汉模式
std::mutex lock;
class lazySingleton
{
public:
static lazySingleton *getObject()
{
// 只有为空的时候才进行初始化
if (_pinst == nullptr)
{
{
std::unique_lock<std::mutex> ul(lock);
if (_pinst == nullptr)
_pinst = new lazySingleton();
}
}
return _pinst;
}
private:
lazySingleton() {}
lazySingleton(lazySingleton &) = delete;
lazySingleton &operator=(lazySingleton &) = delete;
private:
// 唯一静态对象
static lazySingleton *_pinst;
};
这样就是优化到最好的饿汉模式代码了!
实际开发过程中:懒汉模式使用的更多,因为懒汉模式拥有延迟加载的效果。线程安全问题可以很简单就解决了!
2.4 C++11实现现代单例模式
C++11有一个新特性:保证局部静态变量初始化和是线程安全的。注意需要使用引用返回,语义更清晰。
// 利用C++11新特性:保证局部静态变量的初始化是线程安全的
class mordenSingleton
{
public:
mordenSingleton &getObject()
{
// 这里C++11直接保证初始化是线程安全的
static mordenSingleton _pinst;
return _pinst;
}
private:
mordenSingleton() {}
mordenSingleton(mordenSingleton &) = delete;
mordenSingleton &operator=(mordenSingleton &) = delete;
};
更多推荐



所有评论(0)