zoukankan      html  css  js  c++  java
  • 【more effective c++读书笔记】【第5章】技术(3)——要求(或禁止)对象产生于heap之中

    一、要求对象产生于heap之中

    方法:让析构函数成为private,构造函数为public。然后导入一个伪的析构函数,来调用真正的析构函数。但是它也妨碍了继承和内含。

    例子:

    //UPNumber.h
    #ifndef UPNUMBER_H
    #define UPNUMBER_H
    
    #include<iostream>
    class UPNumber{
    public:
    	UPNumber() :value(0) { std::cout << "UPNumber()" << std::endl; }
    	UPNumber(int initValue) :value(initValue) { std::cout << "UPNumber(int initValue)" << std::endl; }
    	UPNumber(const UPNumber& rhs) { this->value = rhs.value; }
    	//伪构造函数
    	void destroy() const { delete this; }
    private:
    	~UPNumber() { std::cout << "~UPNumber()" << std::endl; }
    	int value;
    };
    
    #endif
    //main.cpp
    #include "UPNumber.h"
    using namespace std;
    
    int main(){
    	//UPNumber n;//错误,析构函数是private的
    	UPNumber* p = new UPNumber;
    	//delete p;//错误,析构函数是private的
    	p->destroy();
    
    	system("pause");
    	return 0;
    }
    

    另一个方法就是将所有的构造函数都声明为private。这个方法的缺点是类常常有多个构造函数,类的作者必须将它们每一个都声明为private。所以比较容易的方法还是让析构函数成为private,构造函数为public,因为一个类只有一个析构函数。

    上述方法说到妨碍了继承,可以用如下方法解决:令UPNumber的析构函数成为protected。也说到妨碍了内含UPNumber对象,可以用如下方法解决:内含一个UPNumber类的指针,指向UPNumber对象。

    二、禁止对象产生于heap之中

    方法:直接将对象产生于heap之中,总是以new产生出来的。new operator总是调用operator new,可以将operator new声明为private。

    例子:

    //UPNumber.h
    #ifndef UPNUMBER_H
    #define UPNUMBER_H
    
    #include<iostream>
    class UPNumber{
    public:
    	UPNumber() :value(0) { std::cout << "UPNumber()" << std::endl; }
    	UPNumber(int initValue) :value(initValue) { std::cout << "UPNumber(int initValue)" << std::endl; }
    	UPNumber(const UPNumber& rhs) { this->value = rhs.value; }
    	~UPNumber() { std::cout << "~UPNumber()" << std::endl; }
    private:
    	static void* operator new(size_t size);   //禁止UPNumber对象位于heap内
    	static void* operator new[](size_t size) ;//禁止UPNumber对象组成的数组位于heap内
    	static void operator delete(void* ptr);   //禁止UPNumber对象位于heap内
    	static void operator delete[](void* ptr); //禁止UPNumber对象组成的数组位于heap内
    
    	int value;
    };
    
    #endif
    //main.cpp
    #include "UPNumber.h"
    using namespace std;
    
    int main(){
    	UPNumber n1;//可以
    	static UPNumber n2;//也可以
    	//UPNumber* p = new UPNumber;//错误,operator new是private的
    	
    	system("pause");
    	return 0;
    }
    

    将operator new声明为private,往往也会妨碍UPNumber对象被实例化为heap-based继承类对象的基类成分。因为operator new和operator delete都会被继承,所以如果这些函数不在继承类内声明为public,继承类继承的便是基类声明的private版本。

    当UPNumber作为其他类的内嵌对象,UPNumber的operator new为private没有什么影响。

    三、判读某个对象是否位于heap内

    方法有以下几种:

    1、在类中增加静态变量指示是否调用operator new来分配内存;如果不是使用operator new来构造对象,就在构造函数中抛出异常。

    例子:

    //UPNumber.h
    #ifndef UPNUMBER_H
    #define UPNUMBER_H
    
    #include<iostream>
    class UPNumber{
    public:
    	class HeapConstrainViolation{};
    	static void* operator new(size_t size);
    	UPNumber();
    	UPNumber(int initValue);
    	UPNumber(const UPNumber& rhs);
    private:
    	int value;
    	static bool onTheHeap;//用来在构造函数内指示正构造中的对象是否位于heap
    };
    bool UPNumber::onTheHeap = false;
    void* UPNumber::operator new(size_t size){
    	onTheHeap = true;
    	return ::operator new(size);
    }
    UPNumber::UPNumber() :value(0) {
    	if (!onTheHeap)
    		throw HeapConstrainViolation();
    	std::cout << "UPNumber()" << std::endl; 
    }
    UPNumber::UPNumber(int initValue) : value(initValue) { 
    	if (!onTheHeap)
    		throw HeapConstrainViolation();
    	std::cout << "UPNumber(int initValue)" << std::endl; 
    }
    UPNumber::UPNumber(const UPNumber& rhs) {
    	this->value = rhs.value; 
    }
    
    #endif
    //main.cpp
    #include "UPNumber.h"
    using namespace std;
    
    int main(){
    	//UPNumber n;//发生异常
    	UPNumber* p = new UPNumber;
    	delete p;
    	
    	system("pause");
    	return 0;
    }
    

    这种方法有以下几个问题:

    a、考虑如下代码:

    UPNumber* numberArray = new UPNumber[100];

    出现第一个问题是数组由operator new[]分配,这个仍可以自己写一个operator new[]的版本。第二个问题numberArray有100个元素,应该有100次构造函数调用,但整个程序只有一次内存分配,所以在100次构造函数中,只有第一次onTheHeap的值为true,后面调用构造函数时有exception抛出。

    b、考虑如下代码:

    UPNumber* pn = new UPNumber(*new UPNumber);

    我们通常希望函数调用顺序如下:

    1)调用第一个对象的operator new

    2)调用第一个对象的constructor

    3)调用第二个对象的operator new

    4)调用第二个对象的constructor

    但C++不保证这么做,有可能是这样的顺序

    1)调用第一个对象的operator new

    2)调用第二个对象的operator new

    3)调用第一个对象的constructor

    4)调用第二个对象的constructor

    因在步骤1)和2)中设立的onTheHeap的值在步骤3)中被清除了,造成步骤4)的对象认为它不处于heap之中。

    2、根据stack和heap的增长方向来判断内存是否在heap中分配。

    依据的事实是程序的地址空间以线性序列组织而成,其中栈从高地址往低地址生长,堆从低地址往高地址生长。这种方法存在两个问题,一是不具有可移植性;二是区分不了heap和static。

    3、维护一个集合,operator new负责把对象的地址加入到一个由动态分配所形成的集合中,operator delete负责把该对象的地址移除,判断一个对象是否在heap中,就看这个对象的地址是否在集合中。对于全局的operator new和operator delete会有以下三个问题:第一,改变原有的operator new和operator delete的语义;第二,效率低,需要承担沉重的簿记工作;第三,当对象涉及多重继承或虚继承的基类时,会拥有多个地址。

    4、采用抽象abstract mixin base class(抽象混合式基类)。

    抽象基类是一个不能被实例化的基类,至少含有一个纯虚函数;混合类则提供一组定义完好的能力,能够与派生类所提供的其他任何功能兼容。我们可以形成一个抽象混合式基类,用来为继承类提供判读某指针是否以operator new分配出来的能力。

    例子:

    //HeapTracked.h
    #ifndef HEAPTRACKED_H
    #define HEAPTRACKED_H
    
    #include<list>
    #include<iostream>
    
    class HeapTracked{
    public:
    	class MissingAddress {};//异常类
    
    	virtual ~HeapTracked() = 0;
    	static void* operator new(size_t size);
    	static void operator delete(void* ptr);
    	bool isOnHeap() const;
    private:
    	static std::list<const void*> addresses;
    };
    
    std::list<const void*> HeapTracked::addresses;
    
    HeapTracked::~HeapTracked() {}
    
    void* HeapTracked::operator new(size_t size){
    	void* memPtr = ::operator new(size);//取得内存
    	addresses.push_back(memPtr);//将其地址置于list内
    	return memPtr;
    }
    
    void HeapTracked::operator delete(void* ptr){
    	std::list<void const*>::iterator iter = find(addresses.begin(), addresses.end(), ptr);
    	if (iter != addresses.end()){//如果找到符合条件的元素,移除之并释放内存
    		addresses.erase(iter);
    		::operator delete(ptr);
    	}
    	else{//否则表示ptr不是operator new所分配,抛出一个异常
    		throw MissingAddress();
    	}
    }
    
    bool HeapTracked::isOnHeap() const{
    	//取得一个指针,指向*this所占内存的起始处
    	const void* rawAddress = dynamic_cast<const void*>(this);
    	std::list<const void*>::iterator iter = find(addresses.begin(), addresses.end(), rawAddress);
    	return iter != addresses.end();//返回找到与否的消息
    }
    
    #endif 
    //main.cpp
    #include "HeapTracked.h"
    using namespace std;
    
    class Asset :public HeapTracked{
    public:
    	Asset(int initValue) {}
    };
    
    int main(){
    	Asset s(10);
    	if (s.isOnHeap())
    		cout << "is on heap" << endl;
    	else
    		cout << "is not on heap" << endl;
    
    	Asset* p = new Asset(1);
    	if (p->isOnHeap())
    		cout << "is on heap" << endl;
    	else
    		cout << "is not on heap" << endl;
    	delete p;
    
    	system("pause");
    	return 0;
    }
    

    上述Heaptracked这样的mixin类有个缺点,就是不能够使用于内建类型身上,因为像int和char这种内建类型并不继承自任何东西。


    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    【C++模版之旅】静态多态的讨论
    UBI(unsorted block image )块管理
    CSS多级数字序号的目录列表(类似3.3.1.这样的列表序号)
    MyBatis映射文件的resultMap如何做表关联
    爱上演讲的程序猿
    PHP中文汉字验证码
    设计模式之(二)Adapter模式
    sphinx全文检索之PHP使用教程
    [置顶] 【cocos2d-x入门实战】微信飞机大战之十三:游戏场景过渡
    计算机的族谱
  • 原文地址:https://www.cnblogs.com/ruan875417/p/4785418.html
Copyright © 2011-2022 走看看