在C ++ 11中实现递归代理模式

时间:2014-12-28 20:26:26

标签: c++ c++11 recursion proxy proxy-pattern

假设我们有一些Foo对象允许:

cout << myFoo[3];
myFoo[5] = "bar";

这要求代理设计模式(详见Scott Meyers - link

但现在让我们假设每个myFoo [i]也是一个Foo实例。

myFoo[7] = Foo{...};
myFoo[5] = "bar"; // Foo has a Foo(std::string) non-explicit constructor

我已接近实施,但我无法摆脱最后一个令人讨厌的“前向声明/不完整类型”错误。

首先,让我们轻松一点:

    // x = someConstObject[4], so this must be Rvalue access
    //  i.e. someConstObject[4] = ... would be a contradiction / const violation
    const Object  operator[] (const Object& key)  const { 
        return Object{ PyObject_GetItem(p, key.p) };
    }

这是基本的非递归代理模式:

    Proxy operator [] ( const Object& key ) { return Proxy{ *this, key }; }

    class Proxy {
    private:
        const Object& container;
        const Object& key;

    public:
        // at this moment we don't know whether it is 'container[key] = x' or 'x = container[key]'
        Proxy( const Object& c, const Object& k ) : container{c}, key{k}
        { }

        // Rvalue
        // e.g. cout << myList[5] 
        operator Object() const {
            return container[key]; // <-- invokes the original const [] overload
        }

        // Lvalue
        // e.g. myList[5] = foo
        const Object&  operator= (const Object& rhs_ob) {
            PyObject_SetItem( container.p, key.p, rhs_ob.p );
            return rhs_ob; // allow daisy-chaining a = b = c etc.
        }

        #if 0 
        // I think this should come for free, as the above Rvalue handler 
        //     ... collapses a Proxy into an Object

        // e.g. myList[5] = someOtherList[7]
        const Proxy&  operator= (const Proxy& rhs) {
            // Force resolution of rhs into Object
            PyObject_SetItem( pContainerObj->p, pKeyObject->p, static_cast<Object>(rhs).p /* rhs.value->p*/ );
            return rhs;
        }
        #endif
        // ^ Note: allows:
        // e.g. x = y[1] = z[2];  // <-- y[1] must return an Object
        // e.g. if( y[1] = z[2] ) // <-- assigns and then checks that y[1] evaluates to true
    };

不确定我是否需要最后一个处理程序:

无论如何,让它递归,我们需要

    class Proxy : Object {
        :

这意味着我们不能再在Object中定义Proxy,否则我们将得到“尝试从不完整类型建立”编译器错误。

让我们这样做。我们还必须修改构造函数以尽可能填写基类:

class Object::Proxy : public Object {
private:
    const Object& container;
    const Object& key;

public:
    // at this moment we don't know whether it is 'c[k] = x' or 'x = c[k]'
    // If it's 'c[k] = x', setting the base class to c[k] is going to 
    //     either set it to the old value of c[k]
    //     or a None object (if it didn't have any value previously)
    // we had better be certain to make sure the original c[k] overload 
    //     returns None if unsuccessful
    Proxy( const Object& c, const Object& k ) 
        : container{c}, key{k}, Object{c[k]} // <-- might fail!
    { }

然后,由于Object基类,我们不再需要手动处理类型转换为对象:

    // Rvalue
    // e.g. cout << myList[5] hits 'const Object operator[]'
    #if 0
    // it looks as though we don't need to do this given that 
    //    we now have Object as base class
    operator Object() const {
        return container[key];
    }
    #endif

但这就是它变得粗糙的地方。

如果我们将Object :: Proxy的定义移到(实际上是)之后的对象,原来的

    Proxy operator [] ( const Object& key ) { return Proxy{ *this, key }; }

...现在给我们一个错误,因为我们使用了一个不完整的类(代理)。请注意,仅仅将定义移到外部并不能解决返回类型为Proxy的问题。如果它只是代理*我们可以做到。但代理不能。

它似乎是一个Catch-22,我看不到任何干净的解决方案。

有吗?

编辑:在回应暗示设计有缺陷的评论时,请记住Object是指针周围的轻量级包装器。它只有一个PyObject *数据成员。

编辑:我正在寻找的原始代码here

2 个答案:

答案 0 :(得分:1)

你的前提似乎有缺陷。根据定义,Proxy 不是 Object;如果是,那么你首先不会称之为Proxy。然后你可以在没有代理的情况下解决你的问题,就像std::map之类的标准数据类型解决它一样:只需要operator[]在必要时返回对新创建的Object的引用。

您正在寻找类似std::vector<bool>代理模式的内容:operator[]返回Proxy operator=并隐式转换为非代理Object 1}}(对于你真正想要使用该值而不是分配给它的情况)。

class Object {
    struct Proxy {
        PyObject *container;
        PyObject *key;
        Proxy(PyObject *c, PyObject *k): container(c), key(k) {}
        Proxy& operator= (const Object& value) { 
            PyObject_SetItem(container, key, value.p); 
            return *this; 
        }
        operator Object() const {
            PyObject *p = PyObject_GetItem(container, key);
            if (p == nullptr) throw "proxy was not backed by a real object";
            return p;
        }
    };

    PyObject *p;
    Object(PyObject* p): p(p) {}

public:
    Object operator[] (const Object& key) const { 
        return PyObject_GetItem(p, key.p); 
    }
    Proxy operator[] (const Object& key) { return {p, key.p}; }
};

答案 1 :(得分:0)

我最终解决了这个问题。

诀窍是简单地将该类用作自己的代理。

因此,最初Proxy对象提供转换以区分Lvalue和Rvalue访问,我只是将这些转换移回到我原来的Object类中:

    mutable bool m_resolve_me{false};
    PyObject* m_container{nullptr};
    PyObject* m_key{nullptr};

public:
    // Rvalue (e.g. x = ob[42];)
    const Object operator[] (const Object& key)  const { 
        return Object{ PyObject_GetItem( p, key.p ) }; 
    } 

    // Don't know yet
    Object operator[] (const Object& key) { 
        return Object{ *this, key }; 
    }

    // notice we set the m_resolve_me flag
    // as we don't yet know L/Rvalue-ness
    Object( const Object& c, const Object& k )
        : m_container{c.p}, m_key{k.p}, m_resolve_me{true}
    {
        // for all but lvalue access (ob[idx]=...), ob[idx] will be valid
        p = PyObject_GetItem( m_container, m_key ); 

        if( p == nullptr ) {
            // ... However in the case of lvalue access, 
            // PyObject_GetItem will set Python's error indicator
            // so we must flush that error, as it was expected!
            PyErr_Clear();
            p = charge(Py_None);
        }
        // ^ either way, p ends up charged
    }

public:
    // this will attempt to convert ANY rhs to Object, which takes advantage of ALL the above constructor overrides
    Object& operator=( const Object& rhs )
    {
        /*
            1) normal situation
            2) this object is m_resolve_me, and we are assigning 
                 a normal object to it
            3) this object is m_resolve_me, and we are assigning 
                 a m_resolve_me object to it
            4) this object is normal, and we are assigning a m_resolve_me object to it

            1) we need to charge p
            2) same
            3) same
            4) same

            The only important thing is: we have to be neutral to rhs.p
            That means we have to charge it, as we will be 
               subsequently neutralising it in the destructor
         */
        if( &rhs != this )
            *this = charge(rhs.p);

        return *this;
    }

    // (Always) assume charged pointer
    Object& operator=( PyObject* pyob )
    {
        if( m_resolve_me ) {
            PyObject_SetItem( m_container, m_key, pyob );
            m_resolve_me = false;
        }

        set_ptr( pyob );

        return *this;
    }