代理类简介(转载)_yafeilinux的空间_百度空间


文章出处:DIY部落()

代理类其实就是代理模式的应用。Proxy模式为其他对象提供一种代理以控制这个对象的访问。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层,这个访问层也叫代理。Proxy模式是最常见的模式,在我们生活中处处可见。

1、实现二维数组。

C++中数组各个维的大小必须在编译期确定。要在运行期确定数组大小,我们可以开发一个数组类来代替内建的数组,这样就可以在运行期指定数组的大小。例如对二维数组,开发一个Array2D模板。对二维数组元素的访问用arr[4][6]的形式,但是类对象没有operator[][]这样的重载运算符,因此怎样才能使之与内建数组的行为一致呢?如果在Array2D中直接存储二维数组,我们只有operator[]运算符,它只带一个索引参数,因此不能访问二维数组中的元素。但是一维数组可以通过operator[]直接访问数组的元素,而二维数组实际上是一个一维数组,其中每个元素又是一个一维数组。比如arr[4][6]实际上是(arr[4])[6],先取出arr的第4个元素arr[4],这个元素是一维数组,然后在arr[4]这个元素中取出第6个元素。可见,访问Array2D中的元素分两步,我们可以先开发一个一维数组类Array1D,它的operator[]可以直接访问元素,然后在Array2D中存储一个Array1D数组(而不是原始的二维数组数据),Array2D的operator[]只是返回一个一维数组对象,这是{dy}步访问,第二步访问则直接代理给了这个一维数组对象,通过它的operator[]最终得到数组中的元素值。如下:

//Array2D.hpp:二维数组类
#ifndef ARRAY_2D_HPP
#define ARRAY_2D_HPP
#include <cstddef>
template<typename T>
class Array1D{     //一维数组模板
private:
class NotEqualLength{ }; //数组长度不相等时的异常类
std::size_t length; //一维数组长度
T* data;
void copy(Array1D<T> const& rhs){
for(std::size_t i=0;i<length;++i)
data[i]=rhs.data[i];
}
public:
Array1D(std::size_t len):length(len),data(new T[len]){ //T必须要有默认构造函数
}
Array1D(Array1D<T> const& rhs):length(rhs.length),data(new T[rhs.length]){
copy(rhs); //深拷贝
}
Array1D<T>& operator=(Array1D<T> const& rhs){
if(this==&rhs)
return *this;
if(length!=rhs.length)
throw NotEqualLength(); //数组长度不相等,不能赋值
else
copy(rhs); //深拷贝
return *this;
}
~Array1D(){
delete[] data;
}
T const& operator[](std::size_t index) const{ //const版本
return data[index]; //直接返回数组中的元素
}
T& operator[](std::size_t index){ //非const版本
return data[index];
}
std::size_t getLength() const{ //返回数组第1维的大小
return length;
}
std::size_t getElemSum() const{ //返回数组中元素总个数
return length;
}
};
template<typename T>
class Array2D{   //二维数组模板
private:
class NotEqualLength{ };
std::size_t length2,length1; //数组各个维的大小
Array1D<T>* data;
public:
Array2D(std::size_t len2,std::size_t len1)
:length2(len2),length1(len1),data(0){
//为Array1D<T>数组分配原始内存
void* raw=::operator new[](length2*sizeof(Array1D<T>));
data=static_cast<Array1D<T>*>(raw);
//用placement new调用构造函数初始化各个元素的内存
for(std::size_t i=0;i<length2;++i)
new(data+i) Array1D<T>(length1);
}
Array2D(Array2D<T> const& rhs)
:length2(rhs.length2),length1(rhs.length1),data(0){ //拷贝构造:深拷贝
//为Array1D<T>数组分配原始的内存
void* raw=::operator new[](length2*sizeof(Array1D<T>));
data=static_cast<Array1D<T>*>(raw);
//用placement new调用拷贝构造函数来初始化各个元素的内存
for(std::size_t i=0;i<length2;++i)
new(data+i) Array1D<T>(rhs.data[i]);
}
Array2D<T>& operator=(Array2D<T> const& rhs){ //赋值运算符:要深拷贝
if(this==&rhs)
return *this;
//如果有一维不相等,则数组不能赋值,抛出异常
if((length2!=rhs.length2)||(length1!=rhs.length1))
throw NotEqualLength();
else{ //否则进行深拷贝
for(std::size_t i=0;i<length2;++i)
data[i]=rhs.data[i];
}
return *this;
}
~Array2D(){ //没有用new来创建data数组,就不能直接用delete[]来删除data
for(std::size_t i=0;i<length2;++i)
data[i].~Array1D<T>(); //显式调用析构函数销毁各个对象
::operator delete[](static_cast<void*>(data)); //释放内存
}
Array1D<T> const& operator[](std::size_t index) const{ //const版本
return data[index]; //返回索引处的一维数组对象
}
Array1D<T>& operator[](std::size_t index){ //非const版本
return data[index]; //返回索引处的一维数组对象
}
std::size_t getLength2() const{ //返回数组第2维的大小
return length2;
}
std::size_t getLength1() const{ //返回数组第1维的大小
return length1;
}
long getElemSum() const{ //返回数组中的元素总个数
return length1*length2;
}
};
#endif

//Array2Dtest.cpp:对二组数组的测试
#include <cstddef>
#include <iostream>
#include "Array2D.hpp"
int main(){
std::size_t a1=4;
std::size_t a2=5;
Array1D<int> myarr(a1); //数组的各个维数大小在运行期确定
std::cout<<"myarr's length: "<<myarr.getLength()<<std::endl; //输出一维数组长度
for(std::size_t i=0;i<myarr.getLength();++i)
myarr[i]=i;
std::cout<<"myarr[2]: "<<myarr[2]<<std::endl; //输出myarr[2]=2
std::cout<<"myarr's elem-numbers: "<<myarr.getElemSum()<<std::endl; //输出元素总个数
Array1D<int> yourarr(myarr); //测试拷贝构造函数
std::cout<<"yourarr[2]: "<<yourarr[2]<<std::endl;
Array1D<int> herarr(4);
herarr=yourarr; //测试赋值操作符
std::cout<<"herarr[2]: "<<herarr[2]<<std::endl;
Array2D<int> arr(a1,a2);
std::cout<<"arr's length2: "<<arr.getLength2()<<std::endl; //输出二维数组各个维的长度
std::cout<<"arr's length1: "<<arr.getLength1()<<std::endl;
for(std::size_t i=0;i<arr.getLength2();++i)
for(std::size_t j=0;j<arr.getLength1();++j)
arr[i][j]=i+j; //下标访问与内置数组一样
std::cout<<"arr[2][3]: "<<arr[2][3]<<std::endl; //输出arr[2][3]=5
std::cout<<"arr's elem-numbers: "<<arr.getElemSum()<<std::endl; //输出元素总个数
Array2D<int> arr2(arr); //测试拷贝构造函数
std::cout<<"arr2[2][3]: "<<arr2[2][3]<<std::endl;
Array2D<int> arr3(4,5);
arr3=arr2; //测试赋值操作符
std::cout<<"arr3[2][3]: "<<arr3[2][3]<<std::endl;
Array2D<int> arr4(3,5);
arr4=arr3; //抛出异常
return 0;
}

解释:
    (1)Array1D是一维数组模板,它直接存储了一个指向数组的指针data,因此在拷贝和赋值时都要进行深拷贝(对data指向的数据进行拷贝),赋值时还要检查数组长度是否一致,若不一致,则不能赋值,抛出异常。由于要创建T类型的数组,因此T必须要有默认构造函数。当然我们可以用容器比如vector来存放数据,而不用data数组,这样就可以不要求T必须有默认构造函数。Array1D的operator[]直接返回数组中元素的引用。
    (2)Array2D是二维数组模板,它并没有存储二维数组数据,而存储了一个由一维数组对象组成的数组data。注意因为Array1D没有默认的构造函数,它只有一个单参数的构造函数,因此不能直接用new Array1D[len2]来初始化data。当要创建数组但没有默认构造函数时,我们可以用operator new[]来为数组分配原始的内存,然后用placement new表达式调用显式的构造函数来初始化各个元素的内存。对拷贝构造也类似,只不过调用的是拷贝构造函数。这里创建数组并没有用new操作符,因此在析构时不能用delete操作符(用delete是未定义行为,在Linux下出现segmentation fault错误),必须对数组的各个元素显式地调用析构函数来销毁对象,然后调用operator delete[]来释放整个数组内存。
    (3)Array2D的operator[]只是返回下标处的一维数组对象的引用,相当于arr[2],这样对数组元素的访问被代理给了这个一维数组对象arr[2],用它的operator[]最终可以获取到元素的值,即arr[2][3],从测试代码中我们可以看出这个结果。可见,通过代理类Array1D,我们最终实现了与内建行为一致的元素访问语法。这种思想可以推广到多维数组上去。

2、区分operator[]的读操作和写操作。

对于前面“引用计数实现”中介绍的String类,我们通过共享开关实现了一定程度的写时拷贝,但并不xx,它导致有时在读的时候也进行了拷贝。这主要是由于共享开关并不能让operator[]区分读操作和写操作,opeartor[]函数里只是返回下标处字符的引用,然后我们才对这个字符进行读(作右值)或写(作左值)操作,如cout<

//string2.hpp:字符串类,使用代理模式来区分读操作和写操作
#ifndef STRING_HPP
#define STRING_HPP
#include <iostream>
#include <cstring>
#include "rcobject.hpp"
#include "rcptr.hpp"
class String{
private:
//表示字符串内容的内嵌类,实现了引用计数功能
//这个值对象必须在堆上创建
struct StringValue : public RCObject{
char *data;
void init(char const* initValue){
data=new char[strlen(initValue)+1];
strcpy(data,initValue); //对字符串进行拷贝
}
StringValue(char const *initValue){ //值对象的构造
init(initValue);
}
StringValue(StringValue const& rhs){ //值对象的拷贝
init(rhs.data);
}
~StringValue(){
delete[] data;
}
};
RCPtr<StringValue> value; //String对象的内容,用智能指针RCPtr封装它
public:
//字符的代理类
class CharProxy{
private:
String& theString; //代理字符所从属的String对象
int charIndex; //真正字符在String中的下标
public:
CharProxy(String& str,int index):theString(str),charIndex(index){
}
CharProxy& operator=(CharProxy const& rhs){ //代理对象之间的写操作:需要写时拷贝
if(this==&rhs)
return *this;
if(theString.value->isShared()) //若已经被共享,则写时需要拷贝
theString.value=new StringValue(theString.value->data);
theString.value->data[charIndex]=
rhs.theString.value->data[rhs.charIndex]; //写入操作
return *this;
}
CharProxy& operator=(char c){ //原始字符到代理对象的写操作:写时拷贝
if(theString.value->isShared())
theString.value=new StringValue(theString.value->data);
theString.value->data[charIndex]=c; //写入操作
return *this;
}
operator char() const{ //对代理对象的读操作:直接转型为底部字符,无需拷贝
return theString.value->data[charIndex];
}
};



郑重声明:资讯 【代理类简介(转载)_yafeilinux的空间_百度空间】由 发布,版权归原作者及其所在单位,其原创性以及文中陈述文字和内容未经(企业库qiyeku.com)证实,请读者仅作参考,并请自行核实相关内容。若本文有侵犯到您的版权, 请你提供相关证明及申请并与我们联系(qiyeku # qq.com)或【在线投诉】,我们审核后将会尽快处理。
—— 相关资讯 ——