C++学习 15(23.10.28)(生存周期,智能指针)

66 阅读6分钟

1 :对象的生存周期

1.1 生存中周期

在C++中,对象的生存周期由其作用域和存储方式决定。

1 自动变量:自动变量是在函数或块的作用域内定义的变量,它们的生命周期与所在的作用域相同。当程序执行离开该作用域时,这些对象将被销毁。

void foo()
{
    int x = 10; // 自动变量
    {
        int y = 20; // 自动变量
    } // y的生命周期结束
} // x的生命周期结束

2:静态变量:静态变量是在函数或块的作用域内定义的变量,但它们的生命周期与程序的整个运行时间相同。静态变量只会被初始化一次,并且在程序结束时才会被销毁。

void foo()
{
    static int x = 0; // 静态变量
    x++;
    cout << x << endl;
}

int main()
{
    foo(); // 输出1
    foo(); // 输出2
    foo(); // 输出3
    return 0;
}

3:动态分配的变量:使用new运算符分配的对象是在堆上分配的,它们的生命周期由程序员控制。当不再需要这些对象时,需要使用delete运算符将其销毁

int *p = new int(10); // 动态分配的变量
delete p; // 销毁对象

1.2 C++中的作用域指针

在C++中,作用域指针是指在函数或代码块内定义的指针变量。与全局指针不同,作用域指针只能在定义它的函数或代码块内使用,超出该范围就无法访问。

作用域指针的作用是在函数或代码块内部动态分配内存空间,并在函数或代码块结束时释放所分配的内存空间。

#include <iostream>
#include <string>

class Entity
{
public:
    Entity()  //构造函数
    {
        std::cout << "Create Entity!" << std::endl; //创建对象是调用,输出信息
    }

    ~Entity() //析构函数
    {
        std::cout << "Destroy Entity!" << std::endl; //销毁对象时用
    }

};

class ScopedPtr
{
private:
    Entity* m_Ptr; //作用域指针
public:
    ScopedPtr(Entity* ptr)  //ScopedPtr 类构造函数,接受一个 Entity 类型的指针作为参数,
//并将其赋值给 m_Ptr 成员变量。

       : m_Ptr(ptr)
    {
    std::cout<< "作用域指针" << std::endl;
    }

    ~ScopedPtr()   //析构函数
    {
         std::cout << "销毁作用域指针" << std::endl;
         delete m_Ptr;
    }
};

int main()
{
    {
        ScopedPtr e = new Entity();
        //Entity* e = new Entity();

        //Entity e;
    }

    std::cin.get();
}

代码解析:

ScopedPtr 类与 Entity 类的关系是 ScopedPtr 类对 Entity 类的一个封装ScopedPtr 类是用来管理 Entity 类对象的内存空间的

具体来说,ScopedPtr 类包含了一个 Entity 类型的指针成员变量 m_Ptr,它在构造函数中接收一个 Entity 类型的指针作为参数,并将其赋值给 m_Ptr。在析构函数中,ScopedPtr 类会使用 delete 运算符释放通过 m_Ptr 指向的 Entity 对象的内存空间。

因此,ScopedPtr 类可以看作是对 Entity 类对象的一种管理方式,它负责管理对象的内存空间,避免手动调用 delete 运算符来释放内存。这样可以提高代码的可靠性,避免内存泄漏和悬挂指针等问题。

需要注意的是,ScopedPtr 类并不是 Entity 类的派生类或子类,它只是使用 Entity 类型的指针成员变量来管理 Entity 类对象的内存空间。因此,ScopedPtr 类与 Entity 类之间并没有继承关系。

他们之间没有继承关系。

1.3 智能指针

在C++中,智能指针是一种用于管理动态分配的对象的指针类。它们提供了自动化的内存管理,可以帮助避免内存泄漏和悬挂指针等问题。C++标准库提供了以下几种智能指针: 

1:std::unique_ptr      :    std::unique_ptr 是独占式智能指针,它提供了对动态分配对象的独占所有权。每个 std::unique_ptr 只能指向一个对象,当 std::unique_ptr 被销毁时,它所指向的对象也会被销毁。它使用移动语义来转移所有权,不能进行拷贝操作。

2:std::shared_ptr      :     std::shared_ptr 是共享式智能指针,它允许多个指针共享同一个对象。它使用引用计数来跟踪对象的引用数量,当最后一个 std::shared_ptr 被销毁时,对象才会被销毁。它可以进行拷贝操作,每个拷贝会增加引用计数

3 :std::weak_ptr       :      std::weak_ptr 也是共享式智能指针,但它不会增加对象的引用计数。它用于解决 std::shared_ptr 的循环引用问题,可以通过 std::weak_ptr 来**观测一个对象,而不影响对象的生命周期。**可以通过 std::weak_ptr 的 lock() 方法获取一个有效的 std::shared_ptr 对象来访问所观测的对象。

 这些智能指针提供了方便且安全的内存管理方式,可以根据需要选择适合的智能指针类型。使用智能指针可以减少手动管理内存的工作量,并提高代码的可靠性和可维护性。但需要注意的是,智能指针并不能解决所有的内存管理问题,仍然需要谨慎使用和遵循相关的内存管理原则。

插记:端到端

端对端(End-to-End)是一种通信模式,指的是在通信过程中,数据从源端点直接传输到目标端点,中间不经过任何中间节点的干预或处理。在这种通信模式下,源和目标之间的通信是直接的,没有任何中间节点对数据进行解析、加密、转换等操作。

端对端通信可以提高数据传输的安全性和可靠性,因为数据只有在源和目标之间传输,中间节点无法对数据进行监视或篡改。例如,在端对端加密的聊天应用程序中,消息只能由发送方和接收方查看,即使中间节点截获了消息,也无法解密它们。

端对端通信还可以提高网络效率和减少延迟,因为数据不需要在中间节点上进行处理和转发。这种通信模式在互联网、移动通信和物联网等领域得到广泛应用。

new 出来的对象等都需要delete,new的结构是 heap  堆

#include <memory>
#include <iostream>

class Foo {
public:
    Foo() { std::cout << "Foo constructed." << std::endl; }
    ~Foo() { std::cout << "Foo destroyed." << std::endl; }
    void hello() { std::cout << "Hello from Foo." << std::endl; }
};

int main() {
    std::unique_ptr<Foo> ptr(new Foo);
//    ptr->hello();
    return 0;
}

// Output:
// Foo constructed.
// Hello from Foo.
// Foo destroyed.

结果

std::shared_ptr

#include <memory>   //标准库 <memory> 头文件提供了智能指针的定义
#include <iostream>   //输入输出流

class Bar {
public:
    Bar() { std::cout << "Bar constructed." << std::endl; }
    ~Bar() { std::cout << "Bar destroyed." << std::endl; }
    void hello() { std::cout << "Hello from Bar." << std::endl; }
};

int main() {
    std::shared_ptr<Bar> ptr1(new Bar);
    std::shared_ptr<Bar> ptr2 = ptr1;  
//创建了一个名为 ptr2 的 std::shared_ptr 对象,并将其初始化为 ptr1。
//这意味着 ptr2 和 ptr1 现在指向相同的内存块。
    ptr1->hello();
    ptr2->hello();
    return 0;
}

// Output:
// Bar constructed.
// Hello from Bar.
// Hello from Bar.
// Bar destroyed.

注意:ptr2 和 ptr1 现在指向相同的内存块

std:: weak_ptr

#include <iostream>
#include <memory>

class Qux;
class Baz {
public:
    void setQux(std::weak_ptr<Qux> ptr) { m_qux = ptr; }
    ~Baz() { std::cout << "Baz destroyed." << std::endl; }
private:
    std::weak_ptr<Qux> m_qux;
};

class Qux {
public:
    void setBaz(std::shared_ptr<Baz> ptr) { m_baz = ptr; }
    ~Qux() { std::cout << "Qux destroyed." << std::endl; }
private:
    std::shared_ptr<Baz> m_baz;
};

int main() {
    std::shared_ptr<Qux> ptr1(new Qux);
    std::shared_ptr<Baz> ptr2(new Baz);
    ptr1->setBaz(ptr2);
    ptr2->setQux(ptr1);
    return 0;
}

代码解析:

这段代码定义了两个类:Qux 和 Baz。Qux 类有一个成员变量 m_baz,它是一个 std::shared_ptr 类型的智能指针。Baz 类有一个成员变量 m_qux,它是一个 std::weak_ptr 类型的弱引用。

 在 main 函数中,我们创建了两个对象 ptr1 和 ptr2,分别是 Qux 和 Baz 类的实例。然后,我们通过调用 ptr1->setBaz(ptr2) 将 ptr2 作为参数传递给 Qux 对象的 setBaz 方法,将 ptr2 的 shared_ptr 赋值给 m_baz 成员变量。 接下来,我们通过调用 ptr2->setQux(ptr1) 将 ptr1 作为参数传递给 Baz 对象的 setQux 方法,将 ptr1 的 shared_ptr 转换为 weak_ptr 并赋值给 m_qux 成员变量。 这样,Qux 对象持有 Baz 对象的强引用,而 Baz 对象持有 Qux 对象的弱引用。

这种组合可以防止形成循环引用,从而避免内存泄漏。 当 main 函数结束时,ptr1 和 ptr2 的作用域结束,它们会自动释放内存。在对象销毁时,它们的析构函数会被调用,输出相应的消息。 总之,这段代码演示了如何使用 shared_ptr 和 weak_ptr 来管理对象之间的引用关系,防止循环引用和内存泄漏的发生。