上篇文章:45 C++智能指针的原理与模拟实现,内存泄漏与RAII-CSDN博客

代码仓库:橘子真甜 (yzc-YZC) - Gitee.com

目录

一. C/C++类型转换

1.1 C语言类型转换

1.2 C++类型转换

a static_cast

b dynamic_cast

c const_cast 与 volatile

d reinterpret_cast

二. 特殊类设计

2.1 只能在栈定义变量的类

2.2 只能在堆定义变量的类

2.3 单例模式⭐

a 饿汉模式

b 懒汉模式

2.4 C++11实现现代单例模式


一. 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 = &num;
    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;
};

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐