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这种内建类型并不继承自任何东西。


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

  • 相关阅读:
    把影响集中到一个点
    How to avoid Over-fitting using Regularization?
    适定性问题
    Numerical Differentiation 数值微分
    What Every Computer Scientist Should Know About Floating-Point Arithmetic
    Generally a good method to avoid this is to randomly shuffle the data prior to each epoch of training.
    What is the difference between iterations and epochs in Convolution neural networks?
    Every norm is a convex function
    Moore-Penrose Matrix Inverse 摩尔-彭若斯广义逆 埃尔米特矩阵 Hermitian matrix
    perl 类里的函数调用其他类的函数
  • 原文地址:https://www.cnblogs.com/ruan875417/p/4785418.html
Copyright © 2011-2022 走看看