• 【C++ techniques】要求/禁止/判断—对象产生于堆中


    • 有时候我们想让某种对象具有“自杀”的能力,所以我们必须要求对象存在堆中,以便我们调用delete this;
    • 另一些时候,我们要求拥有某种确定性,保证某一些类型绝不会发生内存泄漏,原因是没有任何一个该类型的对象从堆中分配出来;堆空间非常宝贵,所以我们要对象全为栈对象,必须禁止对象产生于堆中。

    要求对象产生于堆中(Heap-Based Objects)

    • 为限制对象产生于heap,我们需阻止clients不得使用new以外的方法产生对象;
    • non-heap objects会在其定义点自动构造,并在其寿命结束时自动析构,所以只需让那些被隐式调用的构造函数和析构函数不合法;
    • 比较好的方法是让destructor成为private,而constructors仍为public;
    • 如此一来,你可以导入一个pseudo destructor函数,用来调用真正的destructor。Clients则调用pseudo-destructor来销毁它们产生的对象。

    例如,假设我们希望确保“表现无限精度”的数值对象只能诞生于heap之中:

    class UPNumber
    {
    public:
    	UPNumber();
    	UPNumber(int initValue);
    	UPNumber(double initValue);
    	UPNumber(const UPNumber& rhs);
     
    private:
    	~UPNumber();//dtor位于private内
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    //Clients于是应该这么写:
    UPNumber n; //错误!(虽然合法,但当n的dtor被隐式调用,就不合法了)
    
    UPNumber* p = new UPNumber; //良好
    ...
    delete p;	//错误!企图调用private destructor
    p->destroy(); //良好
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 只要限制destructor和constructors的运用,便可阻止non-heap objects的诞生,但是,它妨碍了继承和内含:
    class UPNumber{...};	//将dtor或ctor声明为private
    class NonNegativeUPNumber:public UPNumber{...};//错误!dtor或ctors无法通过编译
     
    class Asset
    {
    private:
    	UPNumber value;		//错误!dtor或ctors无法通过编译
    	...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    解决:

    1. 令UPNumber的destructor成为protected(并仍保持其constructors为public),便可以解决继承问题;
    2. “必须内含UPNumber对象”之classes可以修改为“内含一个指针,指向UPNumer”对象:
    class UPNumber{...};	//注意将dtor声明为protected
    class NonNegativeUPNumber:public UPNumber{...}; //derived classes可以调用protected members
     
    class Asset
    {
    public:
    	Asset(int initValue);
    	~Asset();
    	...
    private:
    	UPNumber* value;		
    };
     
    Asset::Asset(int initvalue):value(new UPNumber(initValue))
    {
    	...
    }
     
    Asset::~Asset()
    {
    	value->destroy();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    判断某对象是否位于堆内

    abstract mixin base class(抽象混合式基类):

    • abstract base class是一个不能被实例化的基类;
    • mixin class则提供一组定义完好的能力,能够与其派生类所可能提供的其他任何能力兼容。

    我们可以形成一个所谓的抽象混合式基类,用来为派生类提供“判断某指针是否以operator new分配出来的能力:

    class HeapTracked //mixin class;追踪并记录被operator new返回的指针
    {
    public:
    	class MissingAddress{};
    	
    	virtual ~HeapTracked() = 0;
    	
    	static void* operator new(size_t size);//负责分配内存内存并将条目加入list内
    	static void operator delete(void* ptr);//负责释放内存并从list身上移除条目
    	
    	bool isOnHeap() const; //决定某对象的地址是否在list内
    	
    private:
    	typedef const void* RawAddress;
    	static list<RawAddress> addresses; //list记录所有由operator new返回的指针
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    //HeapTracked的完整实现内容:
    
    list<RawAddress> HeapTracked::addresses;
     
    HeapTracked::~HeapTracked{}
     
    void* HeapTracked::operator new(size_t size)
    {
    	void* memPtr = ::operator new(size);
    	addresses.push_back(memPtr);
    	return memPtr;
    }
     
    void HeapTracked::operator delete(void* ptr)
    {
    	list<RawAddress>::iterator it  = 
    			find(addresses.begin(),addresses.end(),ptr);
    			
    	if(it != addresses.end())
    	{
    		address.erase(it);
    		::operator delete(ptr);
    	}else
    	{
    		throw MissingAddress();
    	}
    }
     
     
    bool HeapTracked::isOnHeap() const
    {
    	//取得一个指针,指向*this所占内存的起始处
    	const void* rawAddress = dynamic_cast<const void*>(this);
    	
    	list<RawAddress>::iterator it = 
    		find(address.begin(),address.end(),rawAddress);
    		
    	return it != address.end();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    禁止对象产生于堆中

    一般而言有3种可能:

    1. 对象被直接实例化;
    2. 对象被实例化为派生类对象内的“基类”成分;
    3. 对象被内嵌于其他对象之中。

    欲阻止clients直接将对象实例化于heap之中,你可以让client无法调用new:定义operator new,将其声明为private

    如果不希望clients将UPNumber对象产生于堆内:

    class UPNumber
    {
    private:
    	static void* operator new(size_t size);
    	static void operator delete(void* ptr);
    	...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果要禁止产生“UPNumer对象所组成的数组”,可以将operator new[]operator delete[]声明为private。

    将operator new声明为private,往往也会妨碍UNumber对象被实例化为heap-based derived class objects的“base class成分”,因为operator new和operator delete会被继承,所以如果这些函数不在derived class声明为public,derived class继承的便是base(s)所声明的private版本:

    class UPNumber{...};
    class NonNegativeUPNumber:  //假设此class未声明operator new
    			public UPNumber
    {
    	...
    };
     
    NonNegativeUPNumber n1;				
    static NonNegativeUPNumber n2;		
    NonNegativeUPNumber* p = new NonNegativeUPNumber;//错误!企图调用private operator new
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果derived class声明一个属于自己的operator new(且为public),类似于,当我们企图分配一个“内含UPNumber对象”的对象,“UNPumber的operator new乃为private“不会带来什么影响:

    class Asset
    {
    public:
    	Asset(int initValue);
    	...
    private:
    	UPNumber value;
    };
     
    Asset* pa = new Asset(100); //没问题,调用的是
    							//Asset::operator new或
    							//::operator new而非UPNumber::operator new
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们曾经希望“如果一个UPNumber对象被构造于heap以外,那么就在UPNumber constructor内抛出异常”,这次我们希望“如果对象被产生于heap内的话,就抛出一个异常”。然而,就像没有任何根据移植性的做法可以判断某地址是否位于heap内一样,我们也没有根据移植性的做法可以判断它是否不在heap内。

  • 相关阅读:
    第56节——redux-toolkit中的createAction——了解
    SpringSecurity 配置与使用(含新 API 替换过时的 WebSecurityConfigurerAdapter)
    Java Math.toRadians()具有什么功能呢?
    Q&A特辑|剪得断,理不乱,一场直播解开关联网络与反团伙欺诈谜团
    Redis -- 基础知识1
    刷新三观的HP-UX系统中的强指针赋值出core问题
    sqli-labs/Less-52
    论文学习——水文时间序列相似性查询的分析与研究
    2023牛客OI赛前集训营-提高组(第二场)B.出租
    怎么查找html元素
  • 原文地址:https://blog.csdn.net/weixin_49347928/article/details/133621955