C++学习

强制转换运算符

C++ 引入了四种不同的强制转换运算符以进行强制转换:

  • const_cast
  • static_cast
  • reinterpret_cast
  • dynamic_cast

C语言强制类型转换缺点:
主要是为了克服C语言强制类型转换的以下三个缺点。

  • 没有从形式上体现转换功能和风险的不同。
    • 例如,将int强制转换成double是没有风险的,而将常量指针转换成非常常量指令, 将基类指针转换成派生类指针都是高风险的,而且后两者带来的风险不同(即可能引发不同种类的错误),C语言的强制类型转换形式对这些不同并不加以区分。
  • 将多态基类指针转换成派生类指针时不检查安全性,即无法判断转换后的指针是否确实指向一个派生类对象。
  • 难以在程序中寻找到底什么地方进行了强制类型转换。强制类型转换是引起程序运行是错误的一个原因,因此在程序出错时,可能就会想到是不是有那些强制类型转换出了问题。

const_cast

  • 仅用于进行去除const属性的转换,它也是四个强制类型转换运算符中唯一能够去除const属性的运算符。
  • 常量对象或者是基本数据类型不允许转化为非常常量对象,只能通过指针和引用来修改
#include <iostream>
#include <string>

int main(int argc, char const *argv[])
{
    const int n = 5;
    const std::string s = "inception";
    // 错误
    std::string t = const_cast<std::string>(s);
    // 错误
    int k = const_cast<int>(n);
    return 0;
}

可以利用const_cast转换为同类型的非const引用或者指针

#include <iostream>
#include <string>

int main(int argc, char const *argv[])
{
    const int n = 5;
    // const_cast只针对指针,引用,this指针
    int* k1 = const_cast<int*>(&n);
    *k1 = 22;
    std::printf("n:%d k1:%d\n", n, *k1);
    int& k2 = const_cast<int&>(n);
    k2 = 33;
    std::printf("n:%d k1:%d, k2:%d\n", n, *k1, k2);
}

常成员函数中去除this指针的const属性

#include <iostream>
#include <string>
class CTest
{
public:
    CTest(): m_nTest(2){}
    // 常成员函数(不能修改成员变量值)
    void foo(int nTest) const{
        // m_nTest = nTest;     错误
        const_cast<CTest*>(this)->m_nTest = nTest;
    }
public:
    int m_nTest;
};

int main(int argc, char const *argv[])
{
    CTest t;
    t.foo(1);
    return 0;
}

static_cast

基于等价于隐式转换的一种转换运算符,可使用于需要明确隐式转换的地方。可以用于低风险的转换。

  • 整型和浮点型
  • 字符与整型
  • 转换运算符
  • 空指针转换为任何目标类型的指针

不可以用于风险高的转换

  • 不同类型的指针之间的相互转换
  • 整型和指针之间的相互转换
  • 不同类型的引用之间的转换
#include <iostream>
class CInt{
public:
    operator int(){
        return m_nInt;
    }
public:
    int m_nInt;
};
int main(int argc, char const *argv[])
{
    int n = 5;
    float f = 10.30f;
    // 本质上,发生了隐式转换
    f = n;
    std::printf("f:%d, n:%d\n", f, n);
    // 整型和浮点型
    f = static_cast<float>(n);
    std::printf("f:%d, n:%d\n", f, n);
    // 字符与整型
    char ch = 'a';
    n = static_cast<int>(ch);
    std::printf("ch:%c, n:%d\n", ch, n);
    // void* 指针的转换
    void* p = nullptr;
    int* pN = static_cast<int*>(p);
    // 转换运算符的方式
    CInt nObj;
    int k1 = nObj;
    int k2 = static_cast<int>(nObj);
    return 0;
}

static_cast 用于基类与派生类的转换过程中,但是没有运行时类型检查。

#include <iostream>
class CFather{
public:
    CFather(){
        m_nTest = 2;
    }
    virtual void foo(){
        std::cout << "CFather()::void foo()" << std::endl;
    }
public:
    int m_nTest;
};
class CSon: public CFather
{
    virtual void foo(){
        std::cout << "CSon()::void foo()" << std::endl;
    }
};

int main(int argc, char const *argv[])
{
    CFather* pFather = nullptr;
    CSon* pSon = nullptr;
    // 父类转子类型(不安全)
    // pSon = pFather;     // 不能通过
    pSon = static_cast<CSon*>(pFather);      // 能通过,不安全,没有提供运行时的检查
    // 子类转父类 (安全)
    pFather = pSon;
    pFather = static_cast<CFather*>(pSon);
    return 0;
}

dynamic_cast

用于具有虚函数的基类与派生类之间的指针或引用的转换。

  • 基类必须具备虚函数
  • 原因:dynamic_cast是运行时类型检查,需要运行时类型信息(RTTI),而这个信息是存储与类的虚函数表关系紧密,只有一个类定义了虚函数,才会有虚函数表。
  • 运行时检查,转型不成功则返回一个空指针
  • 非必要不要使用dynamic_cast,有额外的函数开销

常见的转换方式:

  • 基类指针或引用转派生类指针(必须使用dynamic_cast)
  • 派生类指针或引用转基类指针(可以使用dynamic_cast,但是更推荐使用static_cast)
#include <iostream>
class CFather{
public:
    CFather(){
        m_nTest = 2;
    }
    virtual void foo(){
        std::cout << "CFather()::void foo()" << std::endl;
    }
public:
    int m_nTest;
};
class CSon: public CFather
{
    virtual void foo(){
        std::cout << "CSon()::void foo()" << std::endl;
    }
public:
    int m_nSon;
};

int main(int argc, char const *argv[])
{
    CFather f;
    CSon s;
    CFather* pFather = &f;
    CSon* pSon = &s;

    // 向上转换 子类装父类
    // pFather = static_cast<CFather*>(pSon);
    // 向下转换 父类转子类 (不安全)
    // pSon = static_cast<CSon*>(pFather);
    // pSon->m_nSon = 2;   // 越界
    // 检查出这种转换是不安全的
    // 运行时,检测出被转换的指针的类型(依赖RTTI)
    // 有额外的开销,一般而言只有向下转换是,才必须使用
    pSon = dynamic_cast<CSon*>(pFather);
    if(pSon != nullptr){
        pSon->m_nSon = 2;
    }
    具有多态类型的向下转换是必须使用,其余情况可以不使用
    return 0;
}

reinterpret_cast

用于进行各种

lambda表达式/匿名函数

lambda表达式 是一个源自阿隆佐.邱奇的术语。

lambda表达式是C++11中最重要的性特性之一,而lambda表达式,实际上就是提供了一个类似匿名函数的特性,而匿名函数则是需要一个函数,但是又不想费力去命名一个函数情况下去使用的。这样的场景其实很多很多,所以匿名函数几乎是现代编程语言的标配。

lambda表达式基础

lambda表达式的基础语法如下:

[捕获列表](参数列表)mutable(可选)异常属性->返回类型{
    //函数体
}
[caputrue](params)opt->ret{body;};
  • lambda表达式以一对中括号开始。
  • 跟函数定义一样,我们有参数列表
  • 跟正常的函数定义一样,我们会有一个函数体,里面会有return语句
  • lambda表达式一般不需要说明返回值(相当于auto),有特殊情况需要说明时,则应使用箭头语法的方式
  • 每个lambda表达式都有一个全局唯一的类型,要精确捕捉lambda表达式到一个变量中,只能通过auto声明的方式
#include <iostream>

int main(int argc, char const *argv[])
{
    //lambda表达式就是匿名函数
    //[]捕获列表 ()参数列表 -> 返回值
    int c = [](int a, int b)->int{
        return a + b;
    }(1, 2);
    std::printf("c:%d\n", c);
    auto f = [](int a, int b)->int{
        return a + b;
    };
    c = f(4, 9);
    std::printf("c:%d\n", c);
    // 函数式编程 多线程,并发
    c = [](int a)->int{
        return [a](int b)->int{
            return a + b;
        }(19);
    }(3);
    std::printf("c:%d\n", c);
    auto f1 = [](int a){
        return [a](int b){
            return a + b;
        };
    };
    c = f1(11)(22);
    std::printf("c:%d\n", c);
    return 0;
}

mutable

#include <iostream>
int main(int argc, char const *argv[])
{
    int t = 10;
    // 按值捕获
    auto f = [t]() mutable{
        return ++t;
    };
    auto f1 = [t]() mutable{
        return ++t;
    };
    std::cout << f() << std::endl;
    std::cout << f1() << std::endl;
    std::cout << f() << std::endl;
    std::cout << f1() << std::endl;
    std::cout << t << std::endl;
    return 0;
}

捕获列表

所谓捕获列表,其实可以理解为参数的一种类型,lambda表达式内部函数体在默认情况下是不能够使用函数体外的变量的,这时候捕获列表可以起到传递外部数据的作用,根据传递的行为,捕获列表也分为以下几种

  1. 值捕获
  • 与参数传值类似,值捕获的前期是变量可以拷贝,不同支持则在于,被捕获的变量在lambda表达式被创建时拷贝,而非调用时才拷贝
  1. 应用捕获
  • 与引用传参类似,引用捕获保存的是引用,值会发生变化。
  1. 隐式捕获
  • 手动书写捕获列表有时候是非常复杂的,这种机械的工作可以交给编译器来处理,这时候可以在捕获列表中写一个&或=向编译器声明采用引用或者值捕获

总结一下,捕获提供了lambda表达式对外部值进行使用的功能,捕获列表的最常用的四种形式可以是:

  1. [] 空捕获列表
  2. [name1, name2, …] 捕获一系列变量
  3. [&] 引用捕获,让编译器自行推导捕获列表
  4. [=] 值捕获,让编译器执行推导应用列表
#include <iostream>

int main(int argc, char const *argv[])
{
    int t = 11;
    // t = 33;
    // 按值捕获,捕获的是声明匿名函数时,捕获列表参数的值
    auto f = [t]{
        std::cout << t << std::endl;
    };
    t = 33;
    f();
    // 按引用捕获
    auto f2 = [&t](){
        std::cout << t << std::endl;
        t = 99;
    };
    f2();
    std::cout << t << std::endl;
    int a = 1;
    int b = 2;
    // 捕获多个变量
    [a, b](){
        std::printf("a+b=%d\n", a + b);
    }();
    // 按值捕获所有变量
    [=](){
        std::printf("a+b=%d\n", a + b);
    }();
    // 按引用捕获所有变量
    [&](){
        a++;
        b++;
    }();
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>

int main(int argc, char const *argv[])
{
    std::vector<int> v = {1, 3, 4, 5, 6};
    for(int i = 0; i < v.size(); i++){
        if(v[i] % 2 == 0){
            std::printf("偶数:%d\n", v[i]);
        }else{
            std::printf("奇数:%d\n", v[i]);
        }
    }
    std::printf("=================\n");
    std::for_each(v.begin(), v.end(), [](int n){
    if(n % 2 == 0){
            std::printf("偶数:%d\n", n);
        }else{
            std::printf("奇数:%d\n", n);
        }
    });
    return 0;
}

函数对象包装器function与bind

(1) std::function

  • lambda 表达式的本质是一个函数对象,当lambda表达式的捕获列表为空时,lambda表达式还能够作为一个函数指针进行传递
  • 两种不同的调用方式,一种是将lambda作为函数指针传递进行调用,而另外一种则是直接调用lambda表达式,在C++11中,统一了这些概念,将能够被调用的对象的类型,统一称之为可调用类型,而这种类型,便是通过std::function引入的。
  • C++11 std::function 是一种通用、多态的函数封装,它的实例可以对任何可以调用的目标进行存储、复制和调用操作,它也是对C++中现有的可调用实体的一种类型安全的包裹(相对来说,函数指针的调用不是类型安全的),换句话说,就是函数的容器。当我们有了函数的容器之后便能够更加方便的将函数、函数指针作为对象进行处理。
    (2) std::bind/std::placeholder
  • 而std:bind则是用来绑定函数调用的参数的,它解决的需求是我们有时候可能并不一定能够一次性获得调用某个函数的全部参数,通过这个函数,我们可以将部分调用参数提前绑定到函数身上作为一个新的对象,然后在参数齐全后,完成调用。
#include <iostream>
#include <functional>
#include <algorithm>

class CTest{
public:
    CTest(){}
    int MyTest(int n){
        std::printf("CTest n:%d\n", n);
        return n;
    }
    // 防函数
    int operator()(int n){
        std::printf("operator n:%d\n", n);
        return n;
    }
};
int test(int n){
    std::printf("test n:%d\n", n);
    return n;
}
void test2(int a, int b, int c){
    std::cout << a << b << c << std::endl;
}
int main(int argc, char const *argv[])
{
    // 函数对象包装器
    // 为了函数提供了一种容器(封装)
    // 支持四种函数的封装
    // 1 普通函数
    // 2 匿名函数
    // 3 类成员函数
    // 4 防函数(重载了()运算符的函数)
    test(1);
    // 普通函数
    std::function<int(int)> f = test;
    f(33);
    // 匿名函数
    std::function<int(int)> f2 = [](int n)->int{
        std::printf("f2 n:%d\n", n);
        return n;
    };
    f2(13);
    // 类成员函数
    std::function<int(CTest*, int)> f3 = &CTest::MyTest;
    CTest t;
    f3(&t, 66);
    // 防函数调用 重载()运算符
    t(999);
    std::function<int(CTest*, int)> f4 = &CTest::operator();
    f4(&t, 888);

    // bind 机制
    auto f5 = std::bind(test2, 1, 2, 4);
    f5();
    auto f6 = std::bind(test2, std::placeholders::_2, std::placeholders::_1, 0);
    f6(5, 4);
    return 0;
}

智能指针与RAII

C++ 中最令人头疼的问题是强迫程序员对申请的资源(文件、内存)进行管理,一不小心就会出现泄漏(忘记对申请的资源进行释放)的问题

auto prt = new std::vector<int>();

C++ 的解决方法:RAII

在传统C++里我们只好使用new和delete去对资源进行释放,而C++11引入智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。

解决思路:

  • 利用C++中一个对象出了其作用域会被自动析构,因此我们只需要在构造函数的时候申请空间,而在析构函数(在离开作用域时调用)的时候释放空间,这样,就减轻了程序员在编码过程中,考虑资源释放的问题,这就是RAII。

RAII,完整的英文是Resource Acquisition Initialization,是C++所有持有的资源管理方式。

  • 有少量其他语言,如果D、Ade和Rust也采纳了RAII,但主流的变成语言中,C++是唯一一个依赖RAII来做资源管理的。
  • RAII依托栈和析构函数,来对所有的资源一一包括堆内存在内一一进行管理。对RAII的使用,使得C++不需要类似于Jave那样的垃圾收集方法,也能有效地对内存进行管理。

具体而言,C11的stl中为大家带来了3种智能指针,正确合理的使用可以有效地帮助大家管理资源,当然,在C++的使用智能指针没有像Jave,python这种具备垃圾回收机制的语言那么舒适,毕竟,程序员还需要做一些额外的事情,但是,这远比传统的C或C++更加优雅,3种智能指针分别是:

  • std::shared_ptr 强指针
  • std::unique_ptr
  • std::weak_ptr 弱指针

在早期有一个auto_ptr,这个四种指针在使用有区别

  • auto_ptr 有缺陷是过时的产物
  • std::unique_ptr对auto_ptr的问题进行了修正。
  • std::shared_ptr使用了引用计数,但是会出现循环引用的问题需要配合后面的weak_ptr一起使用。
#include <iostream>

class CTest{    
public:
    CTest();
    ~CTest();
private:
    int* m_pInt;
};

CTest::CTest(){
    m_pInt = new int;
    std::printf("CTest finish \n");
}

CTest::~CTest(){
    if(m_pInt != nullptr){
        delete m_pInt;
    }
    std::printf("~CTest finish \n");
}

int main(int argc, char const *argv[]){
    // 通过了new 在堆上分配了4个字节的空间
    // C++ 需要自己取管理堆内存的申请和释放
    int* p = new int;
    CTest t;
    return 0;
}

引用计数

要正确的理解智能指针,首先必须理解引用计数技术。

深拷贝、浅拷贝的概念

  • 深拷贝优缺点:
  1. 优点:每一个的对象(哪怕是通过拷贝构造函数实例化的对象)的指针都有指向的内存空间,而不是共享,所以在对象析构的时候就不存在重复释放或内存泄漏的问题了。
  2. 缺点:内存开销打
  • 浅拷贝优缺点
  1. 优点:通过拷贝构造函数实例化的对象的指针数据变量指向的共享内存空间,因此内存开销较小。
  2. 缺点:对象析构的时候就可能会重复释放或造成内存泄漏。

鉴于深拷贝和浅拷贝的优缺点,可采用引用计数技术,即减小了内存开销,又避免了堆的重复释放或内存泄漏问题。

举例说明

  • 在深拷贝的情况下,通过拷贝构造或者赋值构造的对象均各自包含自己的指针指向"Helllo"。
  • 但是,显然这样比较浪费内存,存在冗余,那么下面的版本更好:
#include <iostream>
#include <string>
#include <string.h>

class CStudent{
public:
    CStudent(const char* pszName);
    CStudent(CStudent& obj);
    CStudent& operator= (CStudent& obj);
    void release();
    void Show(){
        std::cout << "Show " << (int&)m_pszName << " " << m_pszName << std::endl;
    }
private:
    char* m_pszName;
};

CStudent::CStudent(const char* pszName){
    m_pszName = new char[256];
    strcpy(m_pszName, pszName);
}

CStudent::CStudent(CStudent& obj){
    m_pszName = obj.m_pszName;
}

CStudent& CStudent::operator=(CStudent& obj){
    m_pszName = obj.m_pszName;
    return *this;
}

void CStudent::release(){
    if(m_pszName != nullptr){
        delete m_pszName;
        m_pszName = NULL;
    }
}

int main(int argc, char const *argv[]){
    CStudent stu1("stu1");
    CStudent stu2("stu2");
    CStudent stu3 = stu2;
    stu1.Show();
    stu2.Show();
    stu2.Show();
    stu2.release();
    stu3.Show();
    return 0;
}

这样代理的问题

  • 但是这样同时存在问题,一旦其中一个对象释放了资源,那么所有的其他对象的资源也被释放了。
  • 解决方法:增加一个变量,记录资源使用的次数
#include <iostream>
#include <string>
#include <string.h>

class CStudent{
public:
    CStudent(const char* pszName);
    CStudent(CStudent& obj);
    CStudent& operator= (CStudent& obj);
    ~CStudent();
    void release();
    void Show(){
        std::cout << "Show " << (int&)m_pszName << " " << m_pszName << std::endl;
    }
private:
    char* m_pszName;
    //资源计数器, 当资源计数器减为0时,那么表示该资源可以被重复的释放
    int * m_pCount; 
};

CStudent::CStudent(const char* pszName){
    m_pszName = new char[256];
    m_pCount = new int;
    strcpy(m_pszName, pszName);
    *m_pCount = 1;
}

CStudent::CStudent(CStudent& obj){
    // 浅拷贝
    m_pszName = obj.m_pszName;
    m_pCount = obj.m_pCount;
    (*m_pCount)++;
}

CStudent& CStudent::operator=(CStudent& obj){
    if(obj.m_pszName == m_pszName){
        return *this;
    }
    if(--(*m_pCount) == 0){
        delete m_pszName;
        m_pszName = NULL;
        delete m_pCount;
    }
    m_pszName = obj.m_pszName;
    m_pCount = obj.m_pCount;
    (*m_pCount) ++;
    return *this;
}
CStudent::~CStudent(){
    release();
}

void CStudent::release(){
    if(m_pszName != nullptr && --*m_pCount == 0){
        delete m_pszName;
        m_pszName = NULL;
        delete m_pCount;
        m_pCount = NULL;
    }
}

int main(int argc, char const *argv[]){
    CStudent stu1("stu1");
    CStudent stu2("stu2");
    CStudent stu3 = stu2;
    stu1.Show();
    stu2.Show();
    stu3.Show();
    std::cout << "====================" << std::endl;
    stu2.release();
    stu3.Show();
    stu3.release();
    stu3.Show();
    return 0;
}

最后,我们将该引用计数做一个简易的封装,也就是把引用计数作为一个新的类来使用:

#include <iostream>
#include <string>
#include <string.h>

struct RefValue{
    RefValue(const char* pszName);
    ~RefValue();
    void AddRef();
    void Release();

    char* m_pszName;
    int m_nCount;
};

RefValue::RefValue(const char* pszName){
    m_pszName = new char[strlen(pszName) + 1];
    strcpy(m_pszName, pszName);
    m_nCount = 1;
}

RefValue::~RefValue(){
    if(m_pszName!= nullptr){
        delete m_pszName;
        m_pszName = NULL;
    }
}

void RefValue::AddRef(){
    m_nCount++;
}

void RefValue::Release(){
    if(--m_nCount == 0){
        delete this;
    }
}

class CStudent{
public:
    CStudent(const char* pszName);
    CStudent(CStudent& obj);
    CStudent& operator= (CStudent& obj);
    ~CStudent();
    void release();
    void Show(){
        if(m_pValue->m_nCount > 0){
            std::cout << "Show " << (int&)m_pValue->m_pszName << " " << m_pValue->m_pszName << std::endl;
        }
    }
private:
    RefValue* m_pValue;
};

CStudent::CStudent(const char* pszName){
   m_pValue = new RefValue(pszName);
}

CStudent::CStudent(CStudent& obj){
    // 浅拷贝
    m_pValue = obj.m_pValue;
    m_pValue->AddRef();
}

CStudent& CStudent::operator=(CStudent& obj){
    if(obj.m_pValue == m_pValue){
        return *this;
    }
    m_pValue->Release();
    m_pValue = obj.m_pValue;
    m_pValue->AddRef();
    return *this;
}
CStudent::~CStudent(){
    release();
}

void CStudent::release(){
    m_pValue->Release();
}

int main(int argc, char const *argv[]){
    CStudent stu1("stu1");
    CStudent stu2("stu2");
    CStudent stu3 = stu2;
    stu1.Show();
    stu2.Show();
    stu3.Show();
    std::cout << "====================" << std::endl;
    stu2.release();
    stu3.Show();
    stu3.release();
    stu3.Show();
    return 0;
}

问题
上面的做法能在一定程度上解决资源多次重复申请的浪费,但是仍然存在两个核心的问题。

  • 当前对其中某一个类对象中的资源进行了修改,那么所有引用该资源的对象全部会被修改,这显然是错误的。
  • 当前的计数器作用于Student类,在使用时候,需要强行加上引用计数类,这样复用性不好,使用不方便。

写时拷贝

问题:如果共享资源中的值发生了变化,那么其他使用该共享资源的值如何保持不变?

解决思路:使用引用计数时,当发生共享资源值改变的时候,需要对其资源进行重新的拷贝,这样改变的拷贝的值,而不影响原有的对象中的共享资源。

写时拷贝(COW copy on write),在所有需要改变值的地方,重新分配内存。

#include <iostream>
#include <string>
#include <string.h>

struct RefValue{
    RefValue(const char* pszName);
    ~RefValue();
    void AddRef();
    void Release();

    char* m_pszName;
    int m_nCount;
};

RefValue::RefValue(const char* pszName){
    m_pszName = new char[strlen(pszName) + 1];
    strcpy(m_pszName, pszName);
    m_nCount = 1;
}

RefValue::~RefValue(){
    if(m_pszName!= nullptr){
        delete m_pszName;
        m_pszName = NULL;
    }
}

void RefValue::AddRef(){
    m_nCount++;
}

void RefValue::Release(){
    if(--m_nCount == 0){
        delete this;
    }
}

class CStudent{
public:
    CStudent(const char* pszName);
    CStudent(CStudent& obj);
    CStudent& operator= (CStudent& obj);
    ~CStudent();
    void release();
    void Show(){
        if(m_pValue != nullptr && m_pValue->m_nCount > 0){
            std::cout << "Show " << (int&)m_pValue->m_pszName << " " << m_pValue->m_pszName << std::endl;
        }
    }
    void SetName(const char* pszName);
private:
    RefValue* m_pValue;
};

CStudent::CStudent(const char* pszName){
   m_pValue = new RefValue(pszName);
}

CStudent::CStudent(CStudent& obj){
    // 浅拷贝
    m_pValue = obj.m_pValue;
    m_pValue->AddRef();
}

CStudent& CStudent::operator=(CStudent& obj){
    if(obj.m_pValue == m_pValue){
        return *this;
    }
    m_pValue->Release();
    m_pValue = obj.m_pValue;
    m_pValue->AddRef();
    return *this;
}
CStudent::~CStudent(){
    release();
}

void CStudent::release(){
    m_pValue->Release();
}

void CStudent::SetName(const char* pszName){
    m_pValue->Release();
    m_pValue = new RefValue(pszName);
}

int main(int argc, char const *argv[]){
    CStudent stu1("stu1");
    CStudent stu2("stu2");
    CStudent stu3 = stu2;
    stu1.Show();
    stu2.Show();
    stu3.Show();
    std::cout << "====================" << std::endl;
    stu2.SetName("stu222");
    stu1.Show();
    stu2.Show();
    stu3.Show();
    std::cout << "====================" << std::endl;
    stu2.release();
    stu3.Show();
    stu3.release();
    stu3.Show();
    return 0;
}

智能指针的简易实现

前面,我们学会了如何使用引用计数及写时拷贝,这是理解智能指针必不可少的方法。但是,在实际写代码中,我们其实更倾向于让程序员对于资源的管理没有任何的感知,也就是说,最后让程序员只需要考虑资源的何时申请,对于何时以及资源内部如何计数等问题,统统交给编译器内部自己处理。

智能指针另外一点就是在使用上要像真正的指针一样可以支持取内容*,指针访问成员->等操作,因此,就需要对这些运算符进行重载。

#include <iostream>

// 智能指针:
// 1. 用起来像指针
// 2. 会自己对资源进行释放
class CStudent{
public:
    CStudent(){};
    void test(){
        std::cout << "CStudent test" << std::endl;
        m_nSex = 1;
    }
private:
    char * m_pszBuf;
    int m_nSex;
};

// 创建一个类,利用该类的构造和析构(进出作用域自动被编译调用)的机制
// 来解决资源自动释放的问题

// 智能指针雏形 需要管理资源
class CSmartPtr{
public:
    // 一定要是一个堆对象
    CSmartPtr(CStudent* pObj){
        m_pObj = pObj;
    }
    ~CSmartPtr(){
        if(m_pObj != nullptr){
            delete m_pObj;
        }
    }
    // 让对象用起来像一个指针 重载->运算符
    CStudent* operator ->(){
        return m_pObj;
    }
    CStudent& operator * (){
        return *m_pObj;
    }
    operator bool(){
        return m_pObj != nullptr;
    }
    // 不允许=号运算符重载,拷贝构造
    // CSmartPtr& operator = (CSmartPtr&) = delete;
    // CSmartPtr(CSmartPtr&) = delete; 
    // 使用拷贝移动
    // CSmartPtr& operator = (CSmartPtr& obj){
    //     if(m_pObj != nullptr){
    //         delete m_pObj;
    //     }
    //     m_pObj = obj.m_pObj;
    //     obj.m_pObj = nullptr;
    //     return *this;
    // }
private:
    CStudent * m_pObj;
};
int main(int argc, char const *argv[]){
    // 这里可以完成资源的自动释放 
    // 但是,用起来不像一个指针
    CSmartPtr sp(new CStudent);
    // sp->m_pObj->test();
    sp->test();
    (*sp).test();
    if(sp){
        std::cout << "sp is true" << std::endl;
    }
    // 1. 不允许=号运算符重载,拷贝构造
    // 2. 使用拷贝移动,auto_ptr 98
    // 3. 结合前面的引用计数以及写时拷贝 新的智能指针的写法
    // CSmartPtr sp2 = sp;
    // CSmartPtr sp3(new CStudent);
    // sp3 = sp;
    return 0;
}

智能指针的简易实现

#include <iostream>

class CStudent{
public:
    CStudent(){};
    void test(){
        std::cout << "CStudent test" << std::endl;
        m_nSex = 1;
    }
private:
    char * m_pszBuf;
    int m_nSex;
};

class CrefCount{
    friend class CSmartPtr;
    CrefCount(CStudent* pStu){
        m_pObj = pStu;
        m_nCount = 1;
    }
    ~CrefCount(){
        delete m_pObj;
        m_pObj = NULL;
    }
    void AddRef(){
        m_nCount ++;
    }
    void Release(){
        if(--m_nCount == 0){
            // 这么写,就表示自己一定要是一个堆对象
            delete this;
        }
    }
private:
    CStudent* m_pObj;
    int m_nCount;
};

// 致命问题,CSmartPtr中表示的类型是固定的,是CStudent
// 需要添加模版
class CSmartPtr{
public:
    CSmartPtr(){
        m_pRef = NULL;
    }
    // 一定要是一个堆对象
    CSmartPtr(CStudent* pStu){
        m_pRef = new CrefCount(pStu);
    }
    ~CSmartPtr(){
        m_pRef->Release();
    }
    CSmartPtr(CSmartPtr& obj){
        m_pRef = obj.m_pRef;
        m_pRef->AddRef();
    }
    CSmartPtr& operator=(CSmartPtr& obj){
        if(m_pRef == obj.m_pRef){
            return *this;
        }
        m_pRef->AddRef();
        if(m_pRef != NULL){
            m_pRef->Release();
        }
        m_pRef = obj.m_pRef;
        return *this;
    }
    CStudent* operator->(){
        return m_pRef->m_pObj;
    }
    CStudent** operator&(){
        return &m_pRef->m_pObj;
    }
    CStudent& operator*(){
        return *m_pRef->m_pObj;
    }
    operator CStudent*(){
        return m_pRef->m_pObj;
    }
private:
    CrefCount* m_pRef;
};
int main(int argc, char const *argv[]){
    CStudent* pStu = new CStudent();
    CSmartPtr sp1(pStu);
    CSmartPtr sp2 = sp1;    // 拷贝构造
    // sp2 = sp1;           // 运算符重载
    return 0;
}

智能指针的简易实现 添加模版

#include <iostream>

class CStudent{
public:
    CStudent(){};
    void test(){
        std::cout << "CStudent test" << std::endl;
        m_nSex = 1;
    }
private:
    char * m_pszBuf;
    int m_nSex;
};
template<typename T>
class CSmartPtr;

template<typename T>
class CrefCount{
    friend class CSmartPtr<T>;
public:
    CrefCount(T* pStu){
        m_pObj = pStu;
        m_nCount = 1;
    }
    ~CrefCount(){
        delete m_pObj;
        m_pObj = NULL;
    }
    void AddRef(){
        m_nCount ++;
    }
    void Release(){
        if(--m_nCount == 0){
            // 这么写,就表示自己一定要是一个堆对象
            delete this;
        }
    }
private:
    T* m_pObj;
    int m_nCount;
};

// 致命问题,CSmartPtr中表示的类型是固定的,是CStudent
// 需要添加模版
template<typename T>
class CSmartPtr{
public:
    CSmartPtr(){
        m_pRef = NULL;
    }
    // 一定要是一个堆对象
    CSmartPtr(T* pStu){
        m_pRef = new CrefCount<T>(pStu);
    }
    ~CSmartPtr(){
        m_pRef->Release();
    }
    CSmartPtr(CSmartPtr& obj){
        m_pRef = obj.m_pRef;
        m_pRef->AddRef();
    }
    CSmartPtr& operator=(CSmartPtr& obj){
        if(m_pRef == obj.m_pRef){
            return *this;
        }
        m_pRef->AddRef();
        if(m_pRef != NULL){
            m_pRef->Release();
        }
        m_pRef = obj.m_pRef;
        return *this;
    }
    T* operator->(){
        return m_pRef->m_pObj;
    }
    T** operator&(){
        return &m_pRef->m_pObj;
    }
    T& operator*(){
        return *m_pRef->m_pObj;
    }
    operator T*(){
        return m_pRef->m_pObj;
    }
private:
    CrefCount<T>* m_pRef;
};
int main(int argc, char const *argv[]){
    CStudent* pStu = new CStudent();
    // CrefCount<CStudent> ref(pStu);
    CSmartPtr<CStudent> sp1(pStu);
    CSmartPtr<CStudent> sp2(new CStudent);
    return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值