您好,欢迎来到独旅网。
搜索
您的当前位置:首页智能指针 | static | C++11拓展

智能指针 | static | C++11拓展

来源:独旅网

前言

最近学习解读pytorch源码(详见链接),代码中C++11的部分有很多未尝涉及,遂进行总结。

智能指针

shared_ptr

shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每构造一次,内部的引用计数加1;每析构一次,内部的引用计数减1;减为0时,删除所指向的堆内存。
shared_ptr内部的引用计数是安全的,但是对象的读取需要加锁。
初始化:可以通过构造函数std::make_shared辅助函数reset方法来初始化shared_ptr

#include "stdafx.h"
#include <iostream>
#include <future>
#include <thread>

using namespace std;
class Person
{
public:
    Person(int v) {
        value = v;
        std::cout << "Cons" <<value<< std::endl;
    }
    ~Person() {
        std::cout << "Des" <<value<< std::endl;
    }

    int value;

};

int main()
{
    std::shared_ptr<Person> p1(new Person(1));// Person(1)的引用计数为1

    std::shared_ptr<Person> p2 = std::make_shared<Person>(2);

    p1.reset(new Person(3));// 首先生成新对象,然后引用计数减1,引用计数为0,故析构Person(1)
                            // 最后将新对象的指针交给智能指针

    std::shared_ptr<Person> p3 = p1;//现在p1和p3同时指向Person(3),Person(3)的引用计数为2

    p1.reset();//Person(3)的引用计数为1
    p3.reset();//Person(3)的引用计数为0,析构Person(3)
    return 0;
}

避免循环引用。智能指针最大的一个陷阱是循环引用,循环引用会导致内存泄漏。解决方法是引入weak_ptr

unique_ptr

一些资源(如文件描述符、锁等),为了确保唯一性,同时指向它的指针始终只有一个

  • unique_ptr无复制构造函数和拷贝构造函数
  • unique_ptr有转移构造函数(将原指针所指对象转移到新创建的指针,同时原指针释放对象)
#include <memory>
#include <iostream>
#include <utility>

class Foo {

public:
    Foo() = default;
    Foo(int a) :_a(a) {}
    ~Foo() {}
    int get_a() {
        return _a;
    }
    void set_a(int a) {
        _a = a;
    }
private:
    int _a;

};

std::unique_ptr<Foo> change_a(std::unique_ptr<Foo> f)
{
    f->set_a(10);
    return f;
}

int main()
{
    std::unique_ptr<Foo> pf = std::make_unique<Foo>(10);

    //如下操作无法实现:
    // std::unique_ptr<Foo> pf1 = pf;  // compile error
    // auto p = change_a(pf);   //compile error


    //--------------------move---------------------------
    //std::move(lvalue)将左值转换为右值,使接下来的转移成为可能。
    //pf为左值,转换为右值才可以对unique_ptr进行转移赋值
    //如下转移赋值后,pf释放对象,p指向对象
    //--------------------forward------------------------
    //std::forward就可以保存参数的左值或右值特性,还可以保持实参的类型、是否const
    auto p = change_a(std::move(pf));
    std::cout << "get_a = " << p->get_a() << std::endl;
    if (!pf)
    {
        std::cout << "pf is nullptr" << std::endl;
    }
    //owner transfer from function
    std::unique_ptr<Foo> pf2 = std::make_unique<Foo>(11);
    std::unique_ptr<Foo> p2 = change_a(std::move(pf2));
    std::cout << "get_a = " << p2->get_a() << std::endl;
    if (!pf2)
    {
        std::cout << "pf2 is nullptr" << std::endl;
    }

    //使用reset
    pf2.reset(new Foo(12));
    std::cout << "pf2 is not null: " << pf2->get_a() << std::endl;

    //release获取原始指针
    Foo* ff = pf2.release();
    if (!pf2)
    {
        std::cout << "pf2 is nullptr" << std::endl;
    }
    std::cout << "ff is not null: " << ff->get_a() << std::endl;

    return 0;
}
  • 运行结果:
get_a = 10
pf is nullptr
get_a = 10
pf2 is nullptr
pf2 is not null: 12
pf2 is nullptr
ff is not null: 12

weak_ptr

解决shared_ptr的循环引用问题,经典错误示例如下:

#include <iostream>
#include <memory>
#include <vector>
using namespace std;

class ClassA
{
public:
    ClassA() { cout << "ClassA Constructor..." << endl; }
    ~ClassA() { cout << "ClassA Destructor..." << endl; }
    shared_ptr<ClassB> pb;  // 在A中引用B
};

class ClassB
{
public:
    ClassB() { cout << "ClassB Constructor..." << endl; }
    ~ClassB() { cout << "ClassB Destructor..." << endl; }
    shared_ptr<ClassA> pa;  // 在B中引用A
};

int main() {
    shared_ptr<ClassA> spa = make_shared<ClassA>();
    shared_ptr<ClassB> spb = make_shared<ClassB>();
    spa->pb = spb;
    spb->pa = spa;
}

weak_ptr是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,即将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数

改进后的代码:

class ClassA
{
public:
    ClassA() { cout << "ClassA Constructor..." << endl; }
    ~ClassA() { cout << "ClassA Destructor..." << endl; }
    weak_ptr<ClassB> pb;  // 在A中引用B
};

class ClassB
{
public:
    ClassB() { cout << "ClassB Constructor..." << endl; }
    ~ClassB() { cout << "ClassB Destructor..." << endl; }
    weak_ptr<ClassA> pa;  // 在B中引用A
};

int main() {
    shared_ptr<ClassA> spa = make_shared<ClassA>();
    shared_ptr<ClassB> spb = make_shared<ClassB>();
    spa->pb = spb;
    spb->pa = spa;
}

运行结果:

ClassA Constructor...
ClassB Constructor...
ClassA Destructor...
ClassB Destructor...
Program ended with exit code: 0

lock(): 判断weak_ptr指向的对象是否被释放,lock()函数返回一个指向共享对象的shared_ptr,否则返回一个空的shared_ptr

class A
{
public:
    A() : a(3) { cout << "A Constructor..." << endl; }
    ~A() { cout << "A Destructor..." << endl; }

    int a;
};

int main() {
    shared_ptr<A> sp(new A());
    weak_ptr<A> wp(sp);
    //sp.reset();

    if (shared_ptr<A> pa = wp.lock())
    {
        cout << pa->a << endl;
    }
    else
    {
        cout << "wp指向对象为空" << endl;
    }
}

boost::intrusive_ptr VS shared_ptr

详见

  • shared_ptr最大陷阱就是用同一个裸指针创建了多个shared_ptr,这会导致这些shared_ptr析构时,因为每个shared_ptr都有自己引用计数,导致这个裸指针多次销毁。
  • 一个裸指针却可以来创建多个intrusive_ptr,因为所使用的对象通过继承包含引用计数功能的基类(将引用计数作为对象的内部成员变量,共用一个计数器)。
  • intrusive_ptr所指向的对象,必须继承包含引用计数功能的基类;shared_ptr不是“侵入式”的,可指向任何类型的对象。

explict

explicit作用: 在C++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。

static

详见链接:

  • 静态成员变量(面向对象)
    静态数据区分配内存,到程序结束释放
    同类的多个对象数据共享

  • 静态成员函数(面向对象)

    1.静态成员函数无法访问非静态成员变量和非静态成员函数
    2.没有this指针的额外开销,静态成员函数与类的全局函数相比速度稍快

  • 静态全局变量(面向过程)
    1.在全局数据区分配内存
    2.全局变量就可以实现变量在文件间的共享 vs 静态全局变量不能被其它文件所用

  • 静态局部变量(面向过程)
    1.在全局数据区分配内存
    2.程序执行到静态局部变量声明处被首次初始化,即以后的函数调用不再进行初始化
    3.没有显式初始化,自动初始化为0
    4.静态局部变量始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域(对比静态全局变量),当定义它的函数或语句块结束时,其作用域随之结束;

  • 静态函数(面向过程)
    它只能在声明它的文件当中可见,不能被其它文件使用

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- dcrkj.com 版权所有 赣ICP备2024042791号-2

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务