智能指针中几种常见的指针实例解析

目录

额外知识点

常用的提示性宏定义符

智能指针

智能指针的基本原理

模拟普通指针的成员访问功能

模拟普通指针的解引用功能

模拟内存的自动释放与地址的拷贝

智能指针的分类

深复制与浅复制

深复制的两种用途

拷贝避免内存重复释放

避免传参时的切除问题

出现切除问题的程序

程序问题说明

解决切除问题的程序

只发生了小小的变化

为什么传入指针比直接将值拷贝到形参中要好呢?

父类指针为何通常作为函数形参使用?

破坏性复制

破坏性复制智能指针的基本实现原理

破坏性指针应用时应注意

调用memory库中的破坏性指针“auto_ptr”进行上述相同操作

C++11新增智能指针:memory库中的unique_ptr智能指针

unique_ptr智能指针应用实例

但是我们可以用move移动语义(语法)对unique_ptr中的内容进行移动


额外知识点

常用的提示性宏定义符

 

函数声明的形参会有_In_和_out_修饰符,_In_和_out_在这里是宏:

_In_

表明这个变量或参数是输入值,即你必须给这个变量填写好以后提交给某个函数去执行

_out_

表明这个是输出值,即你可以传个地址给形参,函数内部会往这个地址写地

这两个宏并不会参与编译,它仅仅是对程序员起到提示作用,让程序员明白如何调用该函数

智能指针

智能指针的基本原理

#include <iostream>  
#include <sstream>  
#include <string>  
using namespace std;  
  
struct Student   
{  
    string name;  
    float mark;  
    string Output;  
  
    Student(string name, float mark)  
    {  
        cout << "Student()的构造函数被调用" << endl;  
        this->name = name;  
        this->mark = mark;  
    }  
    ~Student()  
    {  
        cout << "Student()析构函数被调用" << endl;  
    }  
    explicit operator const char* () // 只能显式的调用类型转换符  
    {  
        ostringstream OutputString;  
        OutputString << this->name << "的成绩为" << this->mark;  
        Output = OutputString.str(); // ostrinstream返回一个string类型的变量  
        return Output.c_str(); // 将string类型的变量转换为char*类型的变量  
    }  
};  
  
template<typename T>  
struct Smart_Pointer  
{  
    T* ObjectPointer;  
    Smart_Pointer(T* Sptr)  
    {  
        cout << "智能指针的构造函数被调用" << endl;  
        ObjectPointer = Sptr;  
    }  
    ~Smart_Pointer()  
    {  
        cout << "智能指针的析构函数被调用" << endl;  
        delete ObjectPointer; // 当指针生命周期结束,指针所指向的对象自动销毁  
    }  
    T& operator = (T* Sptr)  
    {  
        this->ObjectPointer = Sptr;  
        return *this;  
    }  
    T& operator * () // 隐含参数为this指针  
    {  
        return *(this->ObjectPointer);  
    }  
    T* operator -> () // 隐含参数为this指针  
    {  
        return this->ObjectPointer;  
    }  
};  
  
int main()  
{  
    Student* stud1 = new Student{ "张三", 89.6 };  
    Smart_Pointer<Student> StudPtr(stud1);  
    cout << StudPtr->mark << endl; // 模拟普通指针访问成员  
    cout << (const char*)*StudPtr << endl; // 模拟普通指针进行解引用操作  
}  

智能指针是一个模板类,智能指针的优点在于“当智能指针的生命周期结束之后,指针所指向的存储空间可以被释放掉,这样就模拟了一个自动管理内存的智能指针的简单行为”。

值得注意的是:使智能指针真正“智能”的是复制构造函数、赋值运算符和析构函数的实现,它们决定了智能指针对象被传递给函数、赋值或离开作用域(即像其他类对象一样被销毁)时的行为。

模拟普通指针的成员访问功能

   T* operator -> () // 隐含参数为this指针  
    {  
        return this->ObjectPointer;  
    }  

模拟普通指针的解引用功能

   T& operator * () // 隐含参数为this指针  
    {  
        return *(this->ObjectPointer);  
    }  

模拟内存的自动释放与地址的拷贝

    Smart_Pointer(T* Sptr)  
    {  
        cout << "智能指针的构造函数被调用" << endl;  
        ObjectPointer = Sptr;  
    }  
    ~Smart_Pointer()  
    {  
        cout << "智能指针的析构函数被调用" << endl;  
        delete ObjectPointer; // 当指针生命周期结束,指针所指向的对象自动销毁  
    }  
    T& operator = (T* Sptr)  
    {  
        this->ObjectPointer = Sptr;  
        return *this;  
    }  

智能指针的分类

智能指针的分类实际上就是内存资源管理策略的分类,可分为如下几类:

深复制

写时复制(Copy on Write,COW)

引用计数

引用链接

破坏性复制

我下面将重点介绍“深复制”与“破坏性复制”。

深复制与浅复制

深复制

说白了就是将被拷贝对象的内容拷贝给自己,这样的话就形成了两个内容相同但是相互独立的两个个体

浅复制

说白了就是把别人的地址拷贝一份给自己,这样的做一般情况下可以,但是当被拷贝对象中有用new动态申请的内存时那就完了,在我和被拷贝对象的生命周期结束之时,同一片内存区域要被重复释放两边,那程序就崩了

深复制的两种用途

将内容拷贝一份,避免内存重复释放

避免传递函数参数时,子类到父类的“切割问题”

拷贝避免内存重复释放

#include <iostream>  
#include <string>  
using namespace std;  
  
struct Student   
{  
    char* name;  
    float mark;  
  
    Student(const char* name, float mark)  
    {  
        if (this->name != name)  
        {  
            cout << "调用Student拷贝构造函数" << endl;  
            this->name = new char[strlen(name) + 1]; // 动态申请空间  
            memset(this->name, strlen(name) + 1, 0); // 初始化字符数组中的每个元素  
            strcpy(this->name, name); // 字符数组的拷贝  
            this->mark = mark;  
        }  
    }  
    ~Student()  
    {  
        if (this->name != NULL)  
        {  
            cout << "调用Student析构函数" << endl;  
            delete[] this->name; // 内存的释放  
        }  
    }  
    friend ostream& operator << (ostream& Output, const Student& stud); // 重载自定义数据类型的输出运算符  
};  
ostream& operator << (ostream& Output, const Student& stud)  
{  
    Output << stud.name << "的成绩为" << stud.mark;  
    return Output;  
}  
  
int main()  
{  
    const char* name = "zhangsan";  
    Student stud1(name, 86.6);  
    cout << stud1 << endl;  
}  

避免传参时的切除问题

出现切除问题的程序

#include <iostream>  
#include <string>  
using namespace std;  
  
class Person  
{  
private:  
    string name;  
    float weight;  
public:  
    Person(string name, float weight)  
    {  
        cout << "调用Person的构造函数" << endl;  
        this->name = name;  
        this->weight = weight;  
    }  
    ~Person()  
    {  
        cout << "调用Person的析构函数" << endl;  
    }  
    virtual void Display() // 虚函数  
    {  
        cout << this->name << "的体重为" << this->weight << endl;  
    }  
};  
  
class Student:public Person   
{  
private:  
    int StudName;  
public:  
    Student(int StudName, string name, float weight) :Person(name, weight)  
    {  
        this->StudName = StudName;  
    }  
    ~Student()  
    {  
        cout << "调用Student的析构函数" << endl;  
    }  
    void Display()  
    {  
        cout << "学生的学号为" << this->StudName << endl;  
    }  
};  
  
void ShowInf(Person person1)  
{  
    person1.Display();  
}  
  
int main()  
{  
    Person person1("张三", 51.6);  
    Student stud1(7, "李四", 52.3);  
    ShowInf(stud1); // 调用的是父类的Display函数并没有按照我们的需求调用子类中的虚函数  
} 

 

程序问题说明

按照我们的要求:子类中将Display()函数被设置成为虚函数,虚函数的作用是“子类同名函数覆盖父类同名函数”,因为子类是父类的更加具体化更加详细化的产物,因此我们通常需要将父类的同名函数声明为虚函数用于用子类具体化的同名函数覆盖父类中的老旧函数。

但是当我们向showing()函数传入子类对象时,系统毫不留情的将子类中除父类外的一切全部删除,只保留了继承过来的父类部分,包括虚函数也一并去掉了,那咋办呀?我们可以尝试用深复制来解决问题。

解决切除问题的程序

#include <iostream>  
#include <string>  
using namespace std;  
  
class Person  
{  
private:  
    string name;  
    float weight;  
public:  
    Person(string name, float weight)  
    {  
        cout << "调用Person的构造函数" << endl;  
        this->name = name;  
        this->weight = weight;  
    }  
    ~Person()  
    {  
        cout << "调用Person的析构函数" << endl;  
    }  
    virtual void Display() // 虚函数  
    {  
        cout << this->name << "的体重为" << this->weight << endl;  
    }  
};  
  
class Student:public Person   
{  
private:  
    int StudName;  
public:  
    Student(int StudName, string name, float weight) :Person(name, weight)  
    {  
        this->StudName = StudName;  
    }  
    ~Student()  
    {  
        cout << "调用Student的析构函数" << endl;  
    }  
    void Display()  
    {  
        cout << "学生的学号为" << this->StudName << endl;  
    }  
};  
  
void ShowInf(Person* person1)  
{  
    person1->Display();  
}  
  
int main()  
{  
    Person person1("张三", 51.6);  
    Student stud1(7, "李四", 52.3);  
    ShowInf(&stud1); // 调用的是父类的Display函数并没有按照我们的需求调用子类中的虚函数  
}  

只发生了小小的变化

void ShowInf(Person* person1)  // 传入参数为父类指针
{  
    person1->Display();  // 调用子类的同名函数,虚函数功能被保留
}  

为什么传入指针比直接将值拷贝到形参中要好呢?

传入指针更加灵活,我们的形参为父类指针,意味着我们的指针只能访问父类的成员变量,但是它不像“参数的值传递”那样直接将没用的东西给去掉了,而是全部保留什么也不变但是我的父类指针只指向父类的成员(只指向有效成员),子类新增的成员是无法通过这个指针来访问呢的。

父类指针为何通常作为函数形参使用?

指针只是一个地址的代名词,当我们变化指针的类型时,只会改变它指向的有效内存空间,因此我们通常选择父类指针作为函数形参,这样我们可以传入所有与它有继承关系的子类指针,只需在函数内部进行强制类型转换又可以访问子类的所有元素。

破坏性复制

破坏性复制是这样一种机制,即在智能指针被复制时,将对象的所有权转交给目标指针并重置原来的指针。从名字我们也可以看出来,破坏性复制就相当于数据的转移,当数据被copy到另一个变量中去时,被拷贝对象的指针就会被重置。

破坏性复制智能指针的基本实现原理

#include <iostream>  
#include <string>  
using namespace std;  
  
class Student  
{  
private:  
    string name;  
    float mark;  
public:  
    Student(string name, float mark)  
    {  
        cout << "调用Student的构造函数" << endl;  
        this->name = name;  
        this->mark = mark;  
    }  
    ~Student()  
    {  
        cout << "调用Student的析构函数" << endl;  
    }  
    void Display()  
    {  
        cout << this->name << "的成绩为" << this->mark << endl;  
    }  
};  
  
template<typename T>  
class Destroy_Pointer  
{  
private:  
    T* Dptr;  
public:  
    Destroy_Pointer() { Dptr = NULL; };  
    Destroy_Pointer(T* Dptr)  
    {  
        this->Dptr = Dptr;  
    }   
    Destroy_Pointer(Destroy_Pointer& Pointer)  
    {  
        if (this->Dptr != Pointer.Dptr)  
        {  
            this->Dptr = Pointer.Dptr;  
            Pointer.Dptr = NULL;  
        }  
    }  
    ~Destroy_Pointer()  
    {  
        if (this->Dptr != NULL)  
        {  
            delete this->Dptr;  
        }  
    }  
    Destroy_Pointer& operator = (Destroy_Pointer& Pointer)  
    {  
        if (this->Dptr != Pointer.Dptr)  
        {  
            this->Dptr = NULL;  
            this->Dptr = Pointer.Dptr;  
            Pointer.Dptr = NULL;  
            return *this;  
        }  
        else  
        {  
            this->Dptr = NULL;  
            Pointer.Dptr = NULL;  
            return *this;  
        }  
    }  
    T& operator * ()  
    {  
        return *Dptr;  
    }  
    T* operator -> ()  
    {  
        return this->Dptr;  
    }  
};  
  
int main()  
{  
    Student* stud1 = new Student("张三", 86.6);  
    Destroy_Pointer<Student> Dptr1(stud1), Dptr2;  
    Dptr2 = Dptr1;  
    //Dptr1->Display(); // 发生错误无法访问,是因为在"Dptr2 = Dptr1"时已经将Dptr1=null  
    Dptr2->Display();  
}  

破坏性指针应用时应注意

经过破坏性复制后,被拷贝的指针已经被置为NULL。鉴于这种智能指针销毁源引用,这也使得它不适合用于STL容器,如std::vector或其他任何动态集合类。这些容器需要在内部复制内容,这将导致指针失效。由于种种原因,不在程序中使用破坏性复制智能指针是明智的选择。

调用memory库中的破坏性指针“auto_ptr”进行上述相同操作

#include <memory>  
#include <iostream>  
#include <string>  
using namespace std;  
  
class Student  
{  
private:  
    string name;  
    float mark;  
public:  
    Student(string name, float mark)  
    {  
        cout << "调用Student的构造函数" << endl;  
        this->name = name;  
        this->mark = mark;  
    }  
    ~Student()  
    {  
        cout << "调用Student的析构函数" << endl;  
    }  
    void Display()  
    {  
        cout << this->name << "的成绩为" << this->mark << endl;  
    }  
};  
  
int main()  
{  
    auto_ptr<Student> Dptr1(new Student("张三", 63.5)), Dptr2;  
    Dptr2 = Dptr1;  
    //Dptr1->Display(); // 不可以调用成员函数,因为Dptr1在复制时已经被摧毁  
    Dptr2->Display();   
}  

C++11新增智能指针:memory库中的unique_ptr智能指针

std::unique_ptr是C++11新增的,与auto_ptr稍有不同,因为它不允许复制和赋值。unique_ptr是一种简单的智能指针,但其复制构造函数和赋值运算符被声明为私有的,因此不能复制它,即不能将其按值传递给函数,也不能将其赋给其他指针,只能用引用的方式传递参数。

总之,unique_ptr比C++11已摒弃的auto_ptr更安全,因为复制和赋值不会导致源智能指针对象无效。它在销毁时释放对象,可帮助您进行简单的内存管理。

unique_ptr智能指针应用实例

#include <memory>  
#include <iostream>  
#include <string>  
using namespace std;  
  
class Student  
{  
private:  
    string name;  
    float mark;  
public:  
    Student(string name, float mark)  
    {  
        cout << "调用Student的构造函数" << endl;  
        this->name = name;  
        this->mark = mark;  
    }  
    ~Student()  
    {  
        cout << "调用Student的析构函数" << endl;  
    }  
    void Display()  
    {  
        cout << this->name << "的成绩为" << this->mark << endl;  
    }  
};  
  
int main()  
{  
    unique_ptr<Student> Dptr1(new Student("张三", 63.5)), Dptr2; // 只能在定义时对智能指针初始化  
    //Dptr2 = Dptr1;  // 禁止拷贝,赋值
    Dptr1->Display();
}  

但是我们可以用move移动语义(语法)对unique_ptr中的内容进行移动

#include <memory>  
#include <iostream>  
#include <string>  
using namespace std;  
  
class Student  
{  
private:  
    string name;  
    float mark;  
public:  
    Student(string name, float mark)  
    {  
        cout << "调用Student的构造函数" << endl;  
        this->name = name;  
        this->mark = mark;  
    }  
    ~Student()  
    {  
        cout << "调用Student的析构函数" << endl;  
    }  
    void Display()  
    {  
        cout << this->name << "的成绩为" << this->mark << endl;  
    }  
};  
  
int main()  
{  
    unique_ptr<Student> Dptr1(new Student("张三", 63.5)), Dptr2; // 只能在定义时对智能指针初始化  
    Dptr2 = move(Dptr1);  
    //Dptr1->Display(); // 由于move函数的使用,Dptr1中的内容被移动到Dptr2中,相当于Dptr1中已经被置空(Dptr1=NULL)  
    Dptr2->Display();  
  
}  

 

猜你喜欢

转载自blog.csdn.net/weixin_45590473/article/details/108195576
今日推荐