【C++】类的数据成员选择指针还是引用类型

问题背景

  1. 外部已经以数组形式存储全体影像数据images
  2. 自定义类Triangle需要绑定一张或多张影像,但不想存储影像索引(否则传参时每次还需额外传入images)

问题陈述

此时类Triangle关于影像的数据成员可存储为什么形式

  1. 直接存储 Image 对象,相当于复制
    由于影像需要存储三通道色彩,数据量极大,不合适

  2. 存储 Image* 对象指针
    使用指针最怕就是同一地址有多个指针,某个指针释放了内存资源,其他指针变成病态的情况。但这并不意味着应当杜绝多个指针指向同一地址的情况,而是需要 确保delete后不再有其他指针指向已释放的内存。不过也不必担心会出现开发者不知道的释放情况,因为动态分配的内存只支持显式释放,也就是需要自己写delete。

    当然确实会出现一些自己没控制到的释放情况。比如说用了其他开发者定义的类,类的析构函数中显式 delete 了动态内存资源。但开发者有时需要拷贝该类对象class c0到临时变量class c1中,若临时对象c1释放,则c0中的相应资源其实也被释放了。(当然这种情况的前提是使用编译器默认合成的拷贝控制函数/运算符)。这时候可能会想到用智能指针省心省事,但也并不一定如此。

  3. 存储 std::shared_ptr<Image> 即对象的智能指针
    由于外部存储的为std::vector<Image> images,如果在初始化Triangle时,使用std::make_shared<Image>构造智能指针,那么当该Triangle对象全部销毁时,智能指针计数为0,销毁了影像数据,此时对应的images什么都不存储,如果后续还要用到images,是未定义行为。该做法需要用户保证Triangle对象和images的生存周期一致,而开发者将代码稳定性的希望寄托在用户身上,是极其不明智的。

    不要在已经存在对象或者对象指针的前提下构造智能指针,否则相当于开放了两个独立的释放资源的方式。

  4. 存储 Image& 对象引用
    引用和指针使用上最大的不同在于,引用需要初始化且中途无法变更。由此产生一系列问题。

    // 为该类对象的数组调用 std::sort 时报错
    1> error C2280: 'xx &xx::operator =(const xx &)': attempting to reference a deleted function
    1> message : compiler has generated 'xx::operator =' here
    1> message : 'xx &xx::operator =(const xx &)': function was implicitly deleted because 'xx' has a data member 'xx::image' of reference type
    1> message : see declaration of 'xx::image'
    // 报错信息显示:当类成员含有引用时,无法合成默认的拷贝赋值运算符
    

    引用只能初始化,不能在中途赋值。而拷贝赋值运算符函数内部正是进行着类成员的重新赋值。因此,包含引用类型的数据成员时,需要重新考虑类对象如何拷贝

    classname(const classname& cn) = default;           // 拷贝构造
    classname& operator=(const classname& cn)=default;  // 拷贝赋值
    

    int & 左值引用 (汇编层面和普通指针一致) 由于引用不是对象,不能定义引用的引用。即int a = 0; int& b = a; int&& c = b;是错误的 。
    int && 右值引用(汇编层面和常量引用一致)即产生临时量来存储常量。右值生存期会延长至右值引用的生存期。通过直接传递临时量的资源,减少对象构造析构操作来提高效率

内存管理的概念/准则

  1. 静态内存:全周期
    栈内存:编译器自动创建和销毁
    堆内存:运行时动态分配(使用 new/delete

  2. new/delete 包含两个步骤:在堆上申请/释放内存资源,在栈上存储/销毁对象指针

  3. 显式销毁:动态分配的资源需要 delete
    隐式销毁:编译器帮忙,即栈内存

  4. 销毁类类型的对象 会释放相应内存资源,因为需要执行类的析构函数。智能指针是类类型
    定义一个包含指针的类类型,在某个函数内申请类对象。类对象包括指针数据在栈内存,但指针指向的 new 出的空间在堆里。若使用编译器默认的析构函数,当类对象离开作用域时,编译器仅回收栈内存即销毁指针,指针所指向的对象什么也不会发生。即如果指针指向的是动态内存,内存将不会被释放。
    销毁内置类型的对象 自身无析构函数,没法显式销毁,离开作用域时编译器回收内存。 指针是内置类型

  5. 拷贝/销毁指针和拷贝/销毁含有指针成员的类对象时,并不对指针所指向的内存进行拷贝和销毁
    只对指针本身进行拷贝和销毁操作,当然如果是销毁内置类型的指针比如 int * 确实会销毁指针本身+销毁其指向的int长度的内存空间。

  6. 是不是没有使用new动态分配的对象都是被分配在栈上

    不是,取决于语境,还可能是静态内存。
    Object obj; 这行语句的含义是,使对象 obj 具有 自动存储(automatic storage) 的性质。所谓 自动存储,意思是这个对象的存储位置取决于其声明所在的上下文。如果这个语句出现在函数内部,那么它就在栈上创建对象。如果这个语句不是在函数内部,而是作为一个类的成员变量,则取决于这个类的对象是如何分配的。
    当然,绝大多数情况我们的类对象都是在函数内部定义的,即在栈上。

解决方案

// 辅助类
struct BitMatrix {
	...
   	inline ~BitMatrix () { delete[] data; data = NULL; }   // 自定义析构释放动态内存
};

// 最终 Triangle 类设计的结构
struct Triangle {
    typedef std::shared_ptr<BitMatrix> BitMatrixPtr;
public:
	Image* pImage;       // 普通指针,因为我外部已经存储了std::vector<Image> 会控制影像数据内存的申请释放
	BitMaskPtr pMask;    // 动态指针, 因为我是用到Triangle类时才申请的这部分内存, 谁申请谁管理

	Triangle (Image& image): id(-1), pImage(&image), pMask(std::make_shared<BitMatrix>(image.rows, image.cols);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值