• 【C++ techniques】利用Proxy classes(代理类)实现:多维数组、区分左/右值运用、限制隐式类型转换


    一、实现多维数组

    C++中支持多维数组的方法:产生一个class,用以表现我们有需要却被语言遗漏的对象;

    //定义一个类模板如下:
    template<class T>
    class Array2D
    {
    public:
    	Array2D(int dim1,int dim2);
    	...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. operator[]重载,令它返回一个Array1D对象;
    2. 对Array1D重载operator[],令它返回原来二维数组中的一个元素:
    template<class T>
    class Array2D
    {
    public:
    	class Array1D
    	{
    	public:
    		T& operator[](int index);
    		const T& operator[](int index) const;
    		...
    	};
    	
    	Array1D operator[](int index);
    	const Array1D operator[](int index) const;
    	...
    };
    
    //调用
    Array2D<float> data(10,20);
    ...
    cout << data[3][6];//ok
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    data[3]获得一个Array1D对象,对该对象再施行operator[],获得原二维数组(3, 6)位置的浮点数;Array2D类的用户不需要知道Array1D类的存在。

    • 凡“用来代表(象征)其他对象”的对象,常被称为proxy objects(替身对象);
    • 用以表现proxy objects者,我们称为proxy classes

    二、区分operator[] 的读写动作

    对于一个proxy,你只有3件事情可做:

    1. 产生它,本例也就是指定它代表哪一个字符串中的哪一个字符;
    2. 以它作为赋值动作的目标(接受端),这种情况下你是对它所代表的字符串内的字符做赋值动作。如果这么使用,proxy代表的将是“调用operator[]函数”的那个字符串的左值运用
    3. 以其他方式使用之。如果这么使用,proxy表现的是“调用operatorp[]函数”的那个字符串的右值运用
    //一个reference-counted String class,
    //其中利用proxy class来区分operator[]的左值运用和右值运用:
    
    class String 	//reference-counted strings见上一篇细节
    {
    public:
    	class CharProxy
    	{
    	public:
    		CharProxy(String& str,int index);				//构造
    		CharProxy& operator = (const CharProxy& rhs);	//左值运用
    		CharProxy& operator = (char c);					//右值运用
    		
    		operator char() const;
    		
    	private:
    		String& theString;						//这个proxy附属的字符串
    		int charIndex;							//这个proxy所代表的字符串字符
    	};
    	
    	const CharProxy operator[](int index) const; //针对const strings
    	CharProxy operator[](int index);			 //针对non-const strings
    	...
    	friend class CharProxy;
    	
    private:
    	RCPtr<StringValue> value;
    };
    
    //调用
    String s1,s2;
    ...
    cout << s1[5];    //合法(右值运用)
    s2[5] = 'x';	  //合法(左值运用)
    s1[3] = s2[8];    //合法(左值、右值)
    
    //String operator[]实现:
    const String::CharProxy String::operator[](int index) const
    {
    	return CharProxy(const_cast<String&>(*this), index);
    }
     
     String::CharProxy String::operator[](int index)
     {
    	 return CharProxy(*this, index);
     } 
    
    • 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
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    每个函数都只是产生并返回“被请求之字符”的一个替代品。没有任何动作施加于此字符身上:我们延缓此等行为,直到知道该行为是“读取”或者“写”。

    operator[]返回的每一个proxy都会记住它所附属的字符串,以及它所在的索引位置:

    String::CharProxy::CharProxy(String& str,int index)
    	:theString(str),charIndex(index){}
    
    //将proxy转换为右值:只需要返回该proxy所表现的字符串副本就行:
     
    String::CharProxy::operator char() const
    {
    	return theString.value->data[index];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    //CharProxy的赋值操作符:
    String::CharProxy&
    String::CharProxy::operator = (const CharProxy& rhs)
    {
    	//如果本字符串与其他String对象共享一个实值
    	//将实值复制一份,供本字符串单独使用
    	if(theString.value->isShared()){
    		theString.value = new StringValue(theString.value->data);
    	}
    	//现在进行赋值动作:将rhs所代表的字符值
    	//赋予*this所代表的字符
    	theString.value->datap[charIndex] = 
    				rhs.theString.value->data[rhs.charIndex];
    	return *this;
    
    //第二个CharProxy赋值操作符和上述传统版本几乎雷同:
    CharProxy& String::CharProxy::operator = (char c)
    {
    	if(theString.value->isShared())
    		theString.value = new StringValue(theString.value->data);
    	
    	theString.value->data[charIndex] = c;
    	return *this;
    }
    
    //将上述两个赋值操作符的重复代码抽出来放进一个私有的CharProxy成员函数
    //然后让两个操作符都去调用它
    
    • 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

    三、限制隐式类型转换

    难点:“对proxy取址”所获取的指针类型和“对真实对象取址”获取的指针类型不同。

    解决:需要在CharProxy类内将取址操作符加以重载:

    class String
    {
    public:
    	class CharProxy
    	{
    	public:
    		...
    		char* operator&();
    		const char* operator() const;
    		...
    	};
    	...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    const char* String::CharProxy::operator() const
    {
    	return &(theString.value->data[charIndex]);
    }
     
    char* String::CharProxy::operator&()
    {
    	//确定“标的字符”所属的字符串实值不为任何其他任何String对象共享
    	if(theString.value->isShared()){
    		theString.value = new StringValue(theString.value->data);
    	}
    	//我们不知道clients会将本函数返回的指针保留多久,所以“目标字符”所属的字符串实值绝不可以被共享
    	theString.value->markUnshanreable();
    	
    	return &(theString.value->data[charIndex]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    总结

    • Proxy类的优点:
      允许我们完成某些几乎不可能完成的行为:多维数组、左值/右值的区分、压抑隐式转换就是其三。

    • Proxy类的缺点:
      如果扮演函数返回值的角色,那些proxy对象将是一种临时对象,需要被产生和销毁,构造和析构带来的成本远大于proxies带来的好处,且软件复杂度和也随之增加。

  • 相关阅读:
    除gRPC之外的另一个选择,IceRPC-支持QUIC
    FreeSWITCH入门到精通系列(三):FreeSWITCH基础概念与架构
    教你如何在优麒麟上搭建 RISC-V 交叉编译环境
    C++ Qt TCP协议,处理粘包、拆包问题,加上数据头来处理
    前端常用设计模式
    Leetcode 【1155. 掷骰子等于目标和的方法数】
    车间生产设备管理有哪些问题?低代码来助力
    Linux_文件权限
    MybatisPlus rewriteBatchedStatements=true 批量插入失效,依然是单条插入问题解决
    读书笔记:《聪明的投资者》
  • 原文地址:https://blog.csdn.net/weixin_49347928/article/details/133706022