在C ++ 11中实现递归代理模式 [英] Implementing Recursive Proxy pattern in C++11

查看:184
本文介绍了在C ++ 11中实现递归代理模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

这需要一个代理设计模式(由Scott Meyers - link



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

  myFoo [7] Foo {...}; 
myFoo [5] =bar; // Foo有一个Foo(std :: string)非显式构造函数

有一个实现,但我不能摆脱一个最后的讨厌的前进声明/不完全类型错误。



首先,让我们:

  // x = someConstObject [4],所以这必须是Rvalue访问
// ie someConstObject [4 ] = ...将是一个矛盾/ const违反
const对象操作符[](const对象和键)const {
return Object {PyObject_GetItem(p,key.p)};
}

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

 代理运算符[](const Object& key){return Proxy {* this,key}; } 

class Proxy {
private:
const Object&容器;
const Object&键;

public:
//此时我们不知道是'container [key] = x'还是'x = container [key]'
代理const Object& c,const Object& k):container {c},key {k}
{}

// Rvalue
// eg cout < myList [5]
operator Object()const {
return container [key]; //< - 调用原始的const []重载
}

// Lvalue
//例如myList [5] = foo
const Object& operator =(const Object& rhs_ob){
PyObject_SetItem(container.p,key.p,rhs_ob.p);
return rhs_ob; //允许菊花链a = b = c等
}

#if 0
//我认为这应该是免费的,如上面的Rvalue处理程序
// ...将代理折叠成一个对象

//例如myList [5] = someOtherList [7]
const Proxy&强制将rhs分解为对象
PyObject_SetItem(pContainerObj-> p,pKeyObject-> p,static_cast< Object>(rhs).p / * rhs.value-> p * /);
return rhs;
}
#endif
// ^注意:允许:
//例如x = y [1] = z [2]; //< - y [1]必须返回一个Object
//例如if(y [1] = z [2])// <分配,然后检查y [1]计算为真
};

不知道我是否需要最后一个处理程序:



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

  class Proxy:Object {

这意味着我们不能再在Object中定义Proxy,否则我们将得到一个 编译器错误。



所以让我们这样做。我们还必须修改构造函数以在可能时填充基类:

  class Object :: Proxy:public Object {
private:
const Object&容器;
const Object&键;

public:
//此时我们不知道是'c [k] = x'还是'x = c [k]'
//如果它的'c [k] = x',将基类设置为c [k]将转到
//或者将其设置为c [k]的旧值
//或无对象(如果它以前没有任何值)
//我们最好一定要确保原来的c [k]重载
//如果不成功返回None如果不成功
Proxy(const Object& c,const Object& k)
:container {c},key {k},Object {c [k]} // <
{}

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

  // Rvalue 
//例如cout < myList [5] hits'const Object operator []'
#if 0
//看起来好像我们不需要这样做,因为
//我们现在有Object as base class
operator Object()const {
return container [key];
}
#endif

p>

如果我们将Object :: Proxy的定义移到(事后)Object对象之外,则原来的

 代理运算符[](const Object& key){return Proxy {* this,key}; } 

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



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



有吗?



编辑:为了回应建议有缺陷的设计的评论,请记住Object是一个围绕指针的轻量级封装。它只有一个PyObject *数据成员。



编辑:我正在工作的原始代码此处

解决方案

我最终解决了这个问题。



窍门是简单地使用类作为自己的代理。
$ b

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

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

public:
// Rvalue(eg x = ob [42];)
const对象操作符[](const对象和键)const {
返回对象{PyObject_GetItem(p,key.p)};
}

//不知道
对象operator [](const Object& key){
return Object {* this,key};
}

//注意我们设置m_resolve_me标志
//因为我们还不知道L / Rvalue-ness
Object(const Object& c, const Object& k)
:m_container {cp},m_key {kp},m_resolve_me {true}
{
//除了左值访问(ob [idx] = ...) ,ob [idx]将有效
p = PyObject_GetItem(m_container,m_key);

if(p == nullptr){
// ...但是在lvalue访问的情况下,
// PyObject_GetItem将设置Python的错误指示符
/ /所以我们必须冲洗这个错误,因为它是预期!
PyErr_Clear();
p = charge(Py_None);
}
// ^无论哪种方式,p结束收费
}

public:
//这将尝试将任何rhs转换为Object,它利用了所有上述构造函数覆盖
Object&运算符=(const Object& rhs)
{
/ *
1)正常情况
2)此对象是m_resolve_me,我们正在分配
a normal对象
3)这个对象是m_resolve_me,我们将
a m_resolve_me对象赋值给它
4)这个对象是正常的,我们为它分配一个m_resolve_me对象

1)我们需要收费p
2)相同
3)相同
4)相同

唯一重要的是:我们必须对rhs .p
这意味着我们必须对它进行收费,因为我们将
随后在析构函数中中和它
* /
if(& rhs!= this)
* this = charge(rhs.p);

return * this;
}

//(始终)假定收费指针
对象& operator =(PyObject * pyob)
{
if(m_resolve_me){
PyObject_SetItem(m_container,m_key,pyob);
m_resolve_me = false;
}

set_ptr(pyob);

return * this;
}


Suppose we have some Foo object that allows:

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

This calls for a proxy design-pattern (detailed by Scott Meyers -- link)

But now let us suppose every myFoo[i] is also a Foo instance.

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

I'm close to having an implementation, but I can't get rid of one final pesky "forward declaration/ incomplete type" error.

Firstly, let's get the easy one out of the way:

    // 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) };
    }

Here is the basic nonrecursive proxy pattern:

    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
    };

Not sure if I need that last handler:

Anyway, making it recursive, we would require

    class Proxy : Object {
        :

And this means that we can no longer define Proxy within Object, otherwise we will get an "attempting to base from incomplete type" compiler error.

So let's do that. And we would also have to modify the constructor to fill in the base class when possible:

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!
    { }

And then, due to the Object base class, we would no longer need to manually handle typecast-to-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

But this is where it gets gnarly.

If we move Object::Proxy's definition outside of (after, in fact) Object, the original

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

... now gives us an error because we have made use of an incomplete class (Proxy). Note that simply moving the definition outside doesn't fix the fact that the return type is Proxy. If only it were Proxy* we could do it. But Proxy cannot.

It appears to be a Catch-22, and I can't see any clean solution.

Is there one?

EDIT: In response to the comment suggesting flawed design, please bear in mind that Object is a lightweight wrapper around a pointer. It has only a single PyObject* data member.

EDIT: The original code I'm working from to be found here

解决方案

I eventually solved this.

The trick is to simply use the class as its own proxy.

So where originally the Proxy object provides conversions to differentiate Lvalue from Rvalue access, I just move these conversions back into my original Object class:

    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;
    }

这篇关于在C ++ 11中实现递归代理模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆