C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++特殊类设计和类型转换

C++特殊类设计(设计模式)和类型转换方式

作者:青瓦梦滋

本文详细解析了单例模式、饿汉模式与懒汉模式的设计方法,并对比了C++中的static_cast、reinterpret_cast与const_cast类型转换方式,同时探讨了dynamic_cast在多态类中的应用,为开发者提供了丰富的设计模式与类型转换知识

在实际应用中,会遇到很多常见的特殊类,本篇会介绍这些类的设计方式

在这之前,虽然没有听过设计模式,但肯定接触过设计模式的例子。

比如迭代器模式,是基于面向对象三大特性之一的封装设计出来的 ,用迭代器类封装后,不暴露容器结构的情况下,用统一的方式访问修改容器中的数据

再比如适配器模式,运用了复用的思想,例如 stack queue 等就是根据底层容器适配而来的

除此之外,还有工厂模式、装饰器模式、观察者模式、单例模式等等,由于大部分设计模式并不常用,因此本篇只会涉及单例模式

不能被拷贝的对象

对于一个对象的拷贝,只会发生在拷贝构造和赋值重载中,那么只需要把这两个成员方法禁用即可

class CopyBan
{
    CopyBan(const CopyBan&) = delete;
    CopyBan& operator=(CopyBan) = delete;
public:
    //成员方法
private:
    //成员变量
};

只能在堆上创建的对象

要像该对象只能在堆上被创建,可以将构造函数定义为私有,再用过统一的接口去创建它

class HeapOnly
{
    HeapOnly(const HeapOnly&) = delete;//防止拷贝
public:
    static HeapOnly* HeapGet()
    {
        return new HeapOnly;
    }
private:
    HeapOnly()//构造函数私有化
    {

    }
};

int main()
{
    HeapOnly h1;//报错
    HeapOnly* h2 = HeapOnly::HeapGet();
    HeapOnly h3 = *h2;//报错
    return 0;
}

只能在栈上创建的对象

既然只能在栈上创建,只需要把统一接口内返回的对象改为栈即可

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

    }
};

int main()
{
    StackOnly s1 = StackOnly::StackGet();
    StackOnly* s2 = new StackOnly;//报错
    return 0;
}

还有一种方法是禁用类的专属operator new,但如果定义static对象时,它是在数据段创建的,而不是在栈上,因此有缺陷:

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

int main()
{
    StackOnly s1;
    StackOnly* s2 = new StackOnly;//报错
    static StackOnly s3;//不报错
    return 0;
}

单例模式

 对于某些类,只应该有一个实例化对象,这就称为单例

单例模式有两种实现方式:

饿汉举例:

构造和析构函数私有化,防止外部调用,并禁用拷贝构造和赋值重载,就可以防止用户构造对象

//饿汉
template<class T>
class singletonHungry
{
private:
    singletonHungry()
    {
        cout << "将构造函数私有化\n";
    }
    ~singletonHungry()
    {
        cout <<"将析构函数私有化\n";
    }
    //防止使用拷贝构造,operator=
    singletonHungry(const singletonHungry&) = delete;
    void operator=(const singletonHungry&) = delete;
public:
    static singletonHungry<T>* getInstance()//static表示该方法属于类本身,不属于具体对象
    {
        return &data;
    }
private:
    static singletonHungry<T> data;//唯一对象
};

template<class T>
singletonHungry<T> singletonHungry<T>::data;//静态成员需再类外声明

int main()
{
    singletonHungry<int>* t = singletonHungry<int>::getInstance();
    return 0;
}

懒汉举例:

在函数内定义局部静态变量该变量的生命周期也是随整个程序,后续如果再次执行定义局部变量的代码,也会因为已经定义过而直接跳过。在多线程中可能存在多个线程同时获取懒汉单例的情况,因此需要注意线程安全!

//懒汉
template<class T>
class singletonLazy
{
private:
    singletonLazy()
    {
        cout << "将构造函数私有化\n";
    }
    ~singletonLazy()
    {
        cout <<"将析构函数私有化\n";
        
    }
    //防止使用拷贝构造,operator=
    singletonLazy(const singletonLazy&) = delete;
    void operator=(const singletonLazy&) = delete;
public:
    
    static singletonLazy<T>& getInstance()//static表示该方法属于类本身,不属于具体对象
    {
        static singletonLazy<T> data;//C++11起,局部静态变量初始化是线程安全的
        return data;
    }
private:
    //成员
};

int main()
{
    singletonLazy<int>& sl = singletonLazy<int>::getInstance();
    return 0;
}

饿汉和懒汉模式的区别:

C++的类型转换

static_cast/reinterpret_cast

在C语言阶段,类型转换分为显式/隐式类型转换两种,隐式类型转换作用于意义相近的类型,显式类型转换(强制类型转换)作用于意义差别大的类型

int i = 1;
double d = 1.5;
i = d;//隐式类型转换

int* pi = nullptr;
pi = i;//报错
pi = (int*)i;//显式类型转换

C++不仅兼容C阶段的显式/隐式类型转换,还有自己的一套转换规范:

在转换时,<>中是要转换为的类型,()中是要转换的数据

int i = 1;
double d = 1.5;
i = static_cast<int>(d);//隐式类型转换

int* pi = nullptr;
pi = i;//报错
pi = reinterpret_cast<int*>(i);//显式类型转换

C++的规范可以让可读性更强

const_cast

 const_cast<>() 用于将const变量在转换时去除const属性(在C语言中就直接用强转)

const int ci = 0;
int* pi1 = const_cast<int*>(&ci);//C++
int* pi2 = (int*)&ci;//C语言阶段

但不管是C语言阶段还是C++阶段,去除const属性后的变量,再改值也不会应用到原变量中

const int ci = 0;
int* pi1 = const_cast<int*>(&ci);//C++
int* pi2 = (int*)&ci;//C语言阶段
*pi1 = *pi2 = 5;
cout << *pi1 << ' ' << *pi2 << ' ' << '\n' << ci;
5 5 
0

这是因为读取带const属性的变量时默认在CPU的寄存器中读取,即使内存中的值改变了,但由于读取时没有访问内存,直接从寄存器中拿,因此值没有变,这是编译器的优化机制。

要想禁用这项编译器优化,可以在定义const变量前加上 volatile 关键字

volatile const int ci = 0;
5 5 
5

dynamic_cast

对于继承类中有虚函数的类,也就是多态类,若一个函数的参数是父类对象的引用/指针父子类都可以传参,若是子类,会发生切片,这个过程是语法天然支持的(向上转换);若函数参数是子类指针/引用,若实参为父类,成功与否要看具体情况(向上转换)

class Parent
{
public:
    virtual void fun()
    {
    }
    int _p;
};

class Child : public Parent
{
public:

    int _c;
};

void funcast(Parent* p)
{
    Child* c = (Child*)p;
    c->_c = 1;
    c->_p = 2;
    cout << c->_c << ' ' << c->_p << '\n';
}

int main()
{
    Parent p;
    Child c;
    funcast(&p);//异常
    funcast(&c);//正常运行
    return 0;
}

 dynamic_cast<>() 用于多态间的类型转换,它可以取代上面强制类型转换的过程,并且指针转换失败时返回nullptr,引用转换失败时报异常

void funcast(Parent* p)
{
    Child* c = dynamic_cast<Child*>(p);
    if(c != nullptr)
    {
        c->_c = 1;
        c->_p = 2;
        cout << c->_c << ' ' << c->_p << '\n';
    }
}

int main()
{
    Parent p;
    Child c;
    funcast(&p);//不会输出,但不报错
    funcast(&c);//正常运行
    return 0;
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文