您的位置:首页 > 编程语言 > C语言/C++

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

2015-09-04 14:47 549 查看
一、要求对象产生于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这种内建类型并不继承自任何东西。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: