问题背景
- 外部已经以数组形式存储全体影像数据
images
- 自定义类
Triangle
需要绑定一张或多张影像,但不想存储影像索引(否则传参时每次还需额外传入images
)
问题陈述
此时类Triangle
关于影像的数据成员可存储为什么形式
-
直接存储
Image
对象,相当于复制
由于影像需要存储三通道色彩,数据量极大,不合适 -
存储
Image*
对象指针
使用指针最怕就是同一地址有多个指针,某个指针释放了内存资源,其他指针变成病态的情况。但这并不意味着应当杜绝多个指针指向同一地址的情况,而是需要 确保delete后不再有其他指针指向已释放的内存。不过也不必担心会出现开发者不知道的释放情况,因为动态分配的内存只支持显式释放,也就是需要自己写delete。当然确实会出现一些自己没控制到的释放情况。比如说用了其他开发者定义的类,类的析构函数中显式 delete 了动态内存资源。但开发者有时需要拷贝该类对象
class c0
到临时变量class c1
中,若临时对象c1
释放,则c0
中的相应资源其实也被释放了。(当然这种情况的前提是使用编译器默认合成的拷贝控制函数/运算符)。这时候可能会想到用智能指针省心省事,但也并不一定如此。 -
存储
std::shared_ptr<Image>
即对象的智能指针
由于外部存储的为std::vector<Image> images
,如果在初始化Triangle
时,使用std::make_shared<Image>
构造智能指针,那么当该Triangle
对象全部销毁时,智能指针计数为0,销毁了影像数据,此时对应的images
什么都不存储,如果后续还要用到images
,是未定义行为。该做法需要用户保证Triangle
对象和images
的生存周期一致,而开发者将代码稳定性的希望寄托在用户身上,是极其不明智的。即 不要在已经存在对象或者对象指针的前提下构造智能指针,否则相当于开放了两个独立的释放资源的方式。
-
存储
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 &&
右值引用(汇编层面和常量引用一致)即产生临时量来存储常量。右值生存期会延长至右值引用的生存期。通过直接传递临时量的资源,减少对象构造析构操作来提高效率
内存管理的概念/准则
-
静态内存:全周期
栈内存:编译器自动创建和销毁
堆内存:运行时动态分配(使用new/delete
) -
new/delete
包含两个步骤:在堆上申请/释放内存资源,在栈上存储/销毁对象指针 -
显式销毁:动态分配的资源需要
delete
隐式销毁:编译器帮忙,即栈内存 -
销毁类类型的对象 会释放相应内存资源,因为需要执行类的析构函数。智能指针是类类型
定义一个包含指针的类类型,在某个函数内申请类对象。类对象包括指针数据在栈内存,但指针指向的new
出的空间在堆里。若使用编译器默认的析构函数,当类对象离开作用域时,编译器仅回收栈内存即销毁指针,指针所指向的对象什么也不会发生。即如果指针指向的是动态内存,内存将不会被释放。
销毁内置类型的对象 自身无析构函数,没法显式销毁,离开作用域时编译器回收内存。 指针是内置类型 -
拷贝/销毁指针和拷贝/销毁含有指针成员的类对象时,并不对指针所指向的内存进行拷贝和销毁
只对指针本身进行拷贝和销毁操作,当然如果是销毁内置类型的指针比如int *
确实会销毁指针本身+销毁其指向的int长度的内存空间。 -
是不是没有使用
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);