I'm trying to wrap a Python PyObject*
in an Object
class.
In Python, everything is a PyObject*
.
A list is a PyObject*
, and each item in the list is itself a PyObject*
.
Which could even be another list.
etc.
I'm trying to allow fooList[42] = barObj
style syntax by means of a Proxy pattern (here).
Now that I have that working, I want to extend it so that fooList[42]
can be used as an Object
. Specifically I want to be able to handle...
fooList[42].myObjMethod()
fooList[42].myObjMember = ...
Object
has a lot of methods, and currently fooList[42].myObjMethod()
is going to first resolve fooList[42]
into a Proxy
instance, say tmpProxy
, and then attempt tmpProxy.myObjMethod()
.
This means I would have to do
void Proxy::myObjMethod(){ return wrapped_ob.myObjMethod(); }
i.e. manually relay each of Object
's methods through Proxy
, which is ugly.
I can't see any perfect solution (see the above linked answer), but I would be happy to use:
fooList[42]->myObjMethod()
... as a compromise, seeing as -> can be overloaded (as opposed to .
which cannot).
However, I can't find any documentation for overloading operator->
.
My best guess is that it must return a pointer to some object (say pObj
), and C++ will invoke pObj->whatever
.
Below is my attempted implementation. However, I'm running into a 'taking the address of a temporary object of type Object' warning.
I have, within my Object
class:
const Object operator[] (const Object& key) const {
return Object{ PyObject_GetItem( p, key.p ) };
}
NOTE that 'const Object&' runs into 'taking the address of a temporary object of type Object' warning.
class Proxy {
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]'
Proxy( const Object& c, const Object& k ) : container{c}, key{k}
{ }
// Rvalue
// e.g. cout << myList[5] hits 'const Object operator[]'
operator Object() const {
return container[key];
}
// Lvalue
// e.g. (something = ) myList[5] = foo
const Proxy& operator= (const Object& rhs_ob) {
PyObject_SetItem( container.p, key.p, rhs_ob.p );
return *this; // allow daisy-chaining a = b = c etc, that's why we return const Object&
}
const Object* operator->() const { return &container[key]; }
// ^ ERROR: taking the address of a temporary object of type Object
};
The idea is to allow myList[5]->someMemberObj = ...
style syntax.
myList[5]
resolves as a Proxy
instance, which is wrapping an Object
(the sixth element of myList
). Let's call it myItem
.
Now I want someProxy->fooFunc()
or someProxy->fooProperty
to invoke myItem.fooFunc()
or myItem.fooProperty
respectively.
I'm running into a 'taking the address of a temporary object of type Object' warning.
The operator-> has special semantics in the language in that, when overloaded, it reapplies itself to the result. While the rest of the operators are applied only once, operator-> will be applied by the compiler as many times as needed to get to a raw pointer and once more to access the memory referred by that pointer.
The class member access operator (->) can be overloaded but it is bit trickier. It is defined to give a class type a "pointer-like" behavior. The operator -> must be a member function. If used, its return type must be a pointer or an object of a class to which you can apply.
You can redefine or overload the function of most built-in operators in C++. These operators can be overloaded globally or on a class-by-class basis. Overloaded operators are implemented as functions and can be member functions or global functions. An overloaded operator is called an operator function.
Operator overloading allows C/C++ operators to have user-defined meanings on user-defined types (classes).
If you can change Object
, you may add
class Object {
public:
// other code
const Object* operator -> () const { return this; }
Object* operator -> () { return this; }
};
And for your Proxy
Object operator->() { return container[key]; }
So, for example
myObj[42]->myFoo = ...
is mostly equivalent to
Proxy proxy = myObj[42];
Object obj = proxy.operator ->();
Object* pobj = obj.operator ->(); // so pobj = &obj;
pobj->myFoo = ...
I find the Proxy
class that you wrote as an example a bit confusing so i took the liberty to change it a little:
Here is a simple example:
//object with lots of members:
class my_obj
{
public:
std::string m_text;
void foo()
{
std::cout << m_text << std::endl;
}
void bar(std::string t)
{
m_text = t;
}
};
//proxy object
class proxy_class
{
private:
friend class CustomContainer;
my_obj* px;
proxy_class(my_obj * obj_px)
:px(obj_px)
{
}
proxy_class() = delete;
proxy_class(const proxy_class &) = delete;
proxy_class& operator =(const proxy_class &) = delete;
public:
my_obj* operator ->()
{
return px;
}
};
//custom container that is the only one that can return proxy objects
class CustomContainer
{
public:
std::map<std::size_t, my_obj> stuff;
proxy_class operator [](const std::size_t index)
{
return proxy_class(&stuff[index]);
}
};
example usage:
CustomContainer cc;
cc[0]->foo();
cc[0]->bar("hello world");
cc[0]->foo();
As a design consideration the proxy class should be create in a controlled environment so constructors are removed from preventing miss-usage.
CustomContainer
has to only return proxy_class
with a reference to my_obj
so it can use anything, std::map
, std::vector
, etc
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With