Реализация шаблона рекурсивного прокси в C ++ 11

Предположим, у нас есть объект Foo, который позволяет:

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

Для этого требуется шаблон дизайна прокси (подробно Скотт Мейерс — ссылка на сайт)

Но теперь давайте предположим, что каждый 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 {
:

А это значит, что мы больше не можем определять Proxy внутри Object, в противном случае мы получим ошибку «попытка основаться на неполном типе».

Итак, давайте сделаем это. И мы также должны были бы изменить конструктор, чтобы заполнить базовый класс, когда это возможно:

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, нам больше не нужно вручную обрабатывать 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

Но это то, где это становится грубым.

Если мы переместим определение Object :: Proxy за пределы (после, фактически) объекта, оригинал

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

… теперь дает нам ошибку, потому что мы использовали неполный класс (Proxy). Обратите внимание, что простое перемещение определения за пределы не фиксирует тот факт, что тип возвращаемого значения — Proxy. Если бы это был Прокси *, мы могли бы это сделать. Но Прокси не может.

Похоже, это Catch-22, и я не вижу чистого решения.

Есть один?

РЕДАКТИРОВАТЬ: В ответ на комментарий, предлагающий некорректный дизайн, пожалуйста, имейте в виду, что Object является легкой оболочкой вокруг указателя. Он имеет только один элемент данных PyObject *.

РЕДАКТИРОВАТЬ: оригинальный код, я работаю, чтобы найти Вот

1

Решение

Ваша предпосылка кажется ущербной. Proxy не является Object, по определению; если бы это было так, то вы бы не назвали это Proxy на первом месте. И тогда вы могли бы решить свою проблему без прокси, так же, как стандартные типы данных, такие как std::map решить это: просто есть operator[] вернуть ссылку на вновь созданный Object когда необходимо.

Вы ищете что-то вроде std::vector<bool>прокси шаблон: operator[] возвращает Proxy с operator= и неявное преобразование в не-прокси Object (для случаев, когда вы действительно хотите использовать значение, а не присваивать ему значение).

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

Другие решения

Я в конце концов решил это.

Хитрость заключается в том, чтобы просто использовать класс в качестве собственного прокси.

Поэтому, если изначально объект 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;
}
0

По вопросам рекламы [email protected]