zoukankan      html  css  js  c++  java
  • More Effective C++ 条款30 Proxy classes(替身类,代理类)

    1. 所谓代理类(proxy class),指的是"它的每一个对象都是为了其他对象而存在的,就像是其他对象的代理人一般".某些情况下用代理类取代某些内置类型可以实现独特的功能,因为可以为代理类定义成员函数而但却无法对内置类型定义操作.条款5就展示了一个使用代理类阻止隐式类型转换的例子.

    2. 实现二维数组.

        C++没有提供分配动态二维数组的语法,因此常常需要定义一些类(模板实现这些功能),像这样:

    template<class T>
    class Array2D {
    public:
        Array2D(int dim1, int dim2);
        ...
    };
    View Code

        既然是二维数组,那么有必要提供使用"[][]"访问元素的操作,然而[][]并不是一个操作符,C++也就不允许重载一个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;
        ...
    };
    View Code

        那么以下操作:

    Array2D<float> data(10, 20);
    ...
    cout << data[3][6];
    View Code

       data[3][6]实际上进行了两次函数调用:第一次调用Array2D的operator[],返回Array1D对象,第二次调用Array1D的operator[],返回指定元素.

       (本例对proxy class作用的体现其实并不显著,因为可以对指针施加operator[],因此只要使Array2D的oeprator[]返回一个指针可达到同样效果,没有必要使用代理类)

    3. 区分operator[]的读写动作

        条款29用String类的例子讨论了引用计数,由于当时无法判断non-const版本oeprator[]返回的字符将被用于读操作还是写操作,因此保险起见,一旦调用non-const版本operator[],便开辟一块新内存并复制数据结构到新内存.在这种策略下,因此如果operator[]返回的字符被用于读操作,那么分配新内存并复制数据结构的行为其实是不必要的,由此会带来效率损失,使用proxy class便可以做到区分non-const operator[]用于读还是写操作,像这样:

    class String {
    public:
        //代理类用于区分operator[]的读写操作
        class CharProxy { // proxies for string chars
        public:
            CharProxy(String& str, int index); // creation
            CharProxy& operator=(const CharProxy& rhs); // lvalue
            CharProxy& operator=(char c); // uses
            operator char() const; 
        private:
            String& theString; //用于操作String,并在适当时机开辟新内存并复制
            int charIndex;
        };
        const CharProxy operator[](int index) const; // for const Strings
        CharProxy operator[](int index); // for non-const Strings
        ...
        friend class CharProxy;
    private:
        RCPtr<StringValue> value;//见条款29
    };
    View Code

        对String调用operator[]将返回CharProxy对象,CharProxy通过重载oeprator char模拟char类型的行为,但它比char类型更有优势——可以为CharProxy定义新的操作,这样当对CharProxy使用operator=时,便可以得知对CharProxy进行写操作,由于CHarProxy保存了父对象String的一个引用,便可以在现在执行开辟内存并复制数据结构的行为,像这样:

    String::CharProxy& String::CharProxy::operator=(const CharProxy& rhs)
    {
        if (theString.value->isShared()) {
            theString.value = new StringValue(theString.value->data);
        }
        theString.value->data[charIndex] = rhs.theString.value->data[rhs.charIndex];
        return *this;
    }
    String::CharProxy& String::CharProxy::operator=(char c)
    {
        if (theString.value->isShared()) {
            theString.value = new StringValue(theString.value->data);
        }
        theString.value->data[charIndex] = c;
        return *this;
    }
    //以上来那个函数的代码部分有重复,可考虑将重复部分提取成一个函数
    View Code

        由于内存开辟和数据结构赋值任务交由CharProxy完成,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);
    }
    View Code

        CharProxy实现的其他部分如下:

    String::CharProxy::CharProxy(String& str, int index): theString(str), charIndex(index) {}
    String::CharProxy::operator char() const
    {
    return theString.value->data[charIndex];
    }
    View Code

    4. 局限性.

        就像智能指针永远无法完全取代内置指针一样,proxy class也永远无法模仿内置类型的所有特点.proxy class可以实现内置类型无法做到功能,但有利有弊——为了模仿内置类型的其他特点,它还要打许多"补丁".

        1). 对proxy class取址.

        条款29通过为StringValue类添加可共享标志(flag)来表示对象是否可被共享以防止外部指针的篡改,其中涉及到对operator[]返回值进行取址操作,这就提示CharProxy也需要对operator&进行重载,像这样:

    class String {
    public:
        class CharProxy {
        public:
            ...
            char * operator&();
            const char * operator&() const;
            ...
        };
        ...
    };
    View Code

        const版本operator&实现比较容易:

    const char * String::CharProxy::operator&() const
    {
        return &(theString.value->data[charIndex]);
    }
    View Code

        non-const版本的operator&要做的事情多一些:

    char * String::CharProxy::operator&()
    {
        //如果正在使用共享内存,就开辟新内存并复制数据结构
        if (theString.value->isShared()) {
            theString.value = new StringValue(theString.value->data);
        }
        //由于有外部指针指向它,因此有被篡改风险,禁止使用共享内存
        theString.value->markUnshareable();
        return &(theString.value->data[charIndex]);
    }
    View Code

        2). 将proxy class传递给接受"references to non-const objects"的函数.

        假设有一个swap函数用于对象两个char的内容:

    void swap(char& a, char& b);

        那么将无法将CharProxy做参数传递给swap,因为swap的参数是char&,尽管CharProxy可以转换到char,但由于抓换后的char是临时对象,仍然无法绑定到char&,解决方法似乎只有对swap进行重载.

        3). 通过proxy cobjects调用真实对象的member function.

        如果proxy class的作用是用来取代内置类型,那么它必须也应该对内置类型能够进行的操作进行重载,如++,+=等,如果它用来取代类类型,那么它也必须具有相同成员函数,使得对该类类型能够进行的操作同样也能够施行于proxy class.

        4). 隐式类型转换.

        proxy class要具有和被代理类型相同的行为,通常的做法是重载隐式转换操作符,正如条款5对proxy class的使用那样,proxy class可以利用"用户定制的隐式类型转换不能连续实行两次"的特点阻止不必要的隐式类型转换,proxy class同样可能因为这个特点而阻止用户需要的隐式类型转换.

    5. 评估

        proxy class的作用很强大,像上面所提到的实现多维数组,区分operator[]的读写操作,压抑隐式类型转换等,但是也有其缺点,如果函数返回proxy class对象,那么它僵尸一个临时对象,产生和销毁它就有可能带来额外的构造和析构成本,此外正如4所讲,proxy class无法完全代替真正对象的行为,尽管大多数情况下真正对象的操作都可由proxy class完成.

  • 相关阅读:
    Excel 小技能
    Socket,ServerSocket,WebSocket
    浅谈Java中的关键字
    线程 学习教程(一): Java中终止(销毁)线程的方法
    OpenGL — GLFW — 颜色
    解决PCL MLS : error LNK2019; error LNK2001 virtual MovingLeastSquares process performProcessing问题
    获取显示屏的个数和分辨率
    Window 显示鼠标的坐标
    OpenCV 鼠标手动绘制掩码图像
    Windows 8 64位系统 在VS2010 32位软件上 搭建 PCL点云库 开发环境
  • 原文地址:https://www.cnblogs.com/reasno/p/4858490.html
Copyright © 2011-2022 走看看