FYI:Proposal: Overloading operator.() & operator.*()
This was recently posted to comp.std.c++, and looks interesting: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1671.pdf Overloading operator.() & operator.*() Gary Powell, Doug Gregor, Jaakko Jarvi Project: Programming Language C++, Evolution Working Group Reply-to: Gary Powell <powellg@amazon.com> Abstract With operator->() and operator->*() a designer of a class can create a smart pointer. There is also a need to create smart references, and smart delayed member access variable hence the need to be able to overload operator.() and operator.*(). -- Regards, Steven
On Wednesday 15 June 2005 23:42, Steven T. Hatton wrote:
This was recently posted to comp.std.c++, and looks interesting:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1671.pdf
Overloading operator.() & operator.*() ... With operator->() and operator->*() a designer of a class can create a smart pointer.
We've been doing that for the YaST2 internal libraries for 5+ years (YCPValue and derived classes, YCPValueRep and derived classes). My personal experience is that this tends to confuse people - you never know when to use "obj.something()" and when to use "obj->something()", much less when to use "const & SomeClass" rather than simply "SomeClass" for function parameters. It obscures what is really going on, and it encourages people to do the wrong thing. I would avoid it for future projects. CU -- Stefan Hundhammer <sh@suse.de> Penguin by conviction. YaST2 Development SUSE Linux Products GmbH Nuernberg, Germany
On Thursday 16 June 2005 05:07, Stefan Hundhammer wrote:
On Wednesday 15 June 2005 23:42, Steven T. Hatton wrote:
This was recently posted to comp.std.c++, and looks interesting:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1671.pdf
Overloading operator.() & operator.*()
...
With operator->() and operator->*() a designer of a class can create a smart pointer.
We've been doing that for the YaST2 internal libraries for 5+ years (YCPValue and derived classes, YCPValueRep and derived classes).
My personal experience is that this tends to confuse people - you never know when to use "obj.something()" and when to use "obj->something()", much less when to use "const & SomeClass" rather than simply "SomeClass" for function parameters. It obscures what is really going on, and it encourages people to do the wrong thing.
I would avoid it for future projects.
I'm confused. Are you saying you used non-standard language features in YaST? How closely did you look at that article? It was proposing a modification to the C++ standard that would permit the overloading of "operator().*", and "operator().". Neither of which are currently overloadable in standard C++. -- Regards, Steven
On Thursday 16 June 2005 19:19, Steven T. Hatton wrote:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1671.pdf
Overloading operator.() & operator.*() ...
We've been doing that for the YaST2 internal libraries for 5+ years (YCPValue and derived classes, YCPValueRep and derived classes). ... I'm confused. Are you saying you used non-standard language features in YaST? How closely did you look at that article? It was proposing a modification to the C++ standard that would permit the overloading of "operator().*", and "operator().". Neither of which are currently overloadable in standard C++.
Uh - sorry, maybe I didn't read the articly you posted thoroughly enough, and maybe my comments were misleading. What we did was to overload operator->() and split up our data container classes into a YCPSomething class and a YCPSomethingRep class. YCPSomethingRep holds the actual data, while YCPSomething is little more than a smart pointer - its overloaded operator->() takes care of actually accessing its corresponding YCPSomethingRep instance. The basic idea was to avoid null (and otherwise invalid) pointers, yet at the same time minimize data copying for function calls. etc. - and to do "shallow copies" whenever possible, since the YCP script language we developed for YaST2 (initially) was designed as a (somewhat) functional language that didn't support the notion of references of pointers, so you basically have to copy around lots of data - in function calls, when returning function results etc.; but of course you really don't want the interpreter to implement that by brute-force copying a function's return value - which might be a big object such as a list or a map where each element in turn can be any kind of big object. Rather, it does a "shallow copy" of those data, thus incrementing the reference count in some YCPSomethingRep object, which prevents it from being freed when the function returns. You get the picture. Still, that architecture tends to confuse people who are using that on the C++ level: You need a YCPSomething, but you have to be aware that this YCPSomething is only a ref-counted pointer, so you access almost all (but not _really_ all, and that's the downside) of its data or functions with YCPSomething->someFunc(), not, as you would expect it, with YCPSomething.someFunc(). Still, you have to check it for validity with YCPSomething.isNull(). OTOH you have to bear in mind that since it already is something like a reference, it makes little sense to pass it to a function with " const YCPSomething &" - you pass the real thing, while OTOH in the same function prototype you really want to use "const std::string &". Confused? Yes, sure, we, too. ;-) Maybe it is possible to hide all that confusion if it is designed and implemented properly, and maybe overloading operator.() and operator*() might help for that purpose. But IMHO since you need to keep thinking anyway when using C++ it's a much better idea not to add yet another layer of abstraction and thus complexity and remain honest with your C++ programmers: Make it absolutely clear what is an object and what is a pointer. Don't add to the confusion. It only adds to the burden they have to carry. CU -- Stefan Hundhammer <sh@suse.de> Penguin by conviction. YaST2 Development SUSE Linux Products GmbH Nuernberg, Germany
On Friday 17 June 2005 06:24, Stefan Hundhammer wrote: ...
Maybe it is possible to hide all that confusion if it is designed and implemented properly, and maybe overloading operator.() and operator*() might help for that purpose.
But IMHO since you need to keep thinking anyway when using C++ it's a much better idea not to add yet another layer of abstraction and thus complexity and remain honest with your C++ programmers: Make it absolutely clear what is an object and what is a pointer. Don't add to the confusion. It only adds to the burden they have to carry.
This may be of interest, particularly the part about pointers and references: http://doc.trolltech.com/qq/qq13-apis.html I agree with Meister Ettrich. OSG uses reference to pass matrices to functions which modify them. That is very deceptive syntax. I believe it's much better to explicitly show that your parameter is subject to modification by using pointers. The reason for proposing the overloading of operator.(), and operator.*() is so that handles can act "just like" the objects they proxy. As it stands, classes such as boost::shared_ptr<> expose the "naked" object in order to provide access to its members using operator.(). The problem with exposing the object directly is that it's one step away from obtaining an unmanaged handle on it. Part of my reason for posting the original article is because I'm trying to develop a uniform, effective and concise approach to the general problem of managing multiple handles on a single object. I'm not yet past the apprentice level in C++ programming, and am finding this one of the more challenging aspects of working with the language. The first few sections of the following suggest that I am not the only one looking for answers in this area. http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1745.pdf The approach OSG uses is to derive many classes from a reference counted baseclass, and make destructors protected. That means that the only way to destroy such objects is by reducing the reference count to 0, and having the internal selfdestructor take care of the rest. Such objects cannot be used as automatic variables (because the destructor can't be called when they go out of scope), and can therefore be somewhat awkward to use. The osg::Referenced derivatives still allow the user to get an unmanaged handle on the object, but they make it easy to participate in the management of the object even if the object is only available as a "raw" pointer. For example, I can have a member variable of a class Bar of type osg::ref_ptr<osg::Geometry> _gometry_rptr; I can then pass the raw pointer to a function - say, a constructor - by using Foo foo = new Foo(_geometry_rptr.get()). Foo could hold onto the pointer by declaring a member osg::Geometry* _geometry_ptr; But there is no protection against Bar reducing the reference count on _geometry_rptr.get() to zero, and thus causing it to be destroyed. If that happens, Foo::_geometry_ptr; is left dangling. Since I know osg::Geometry is derived from osg::Referenced, I can replace Foo{ osg::Geometry* _geometry_ptr; } with Foo{ osg::ref_ptr<osg::Geometry> _geometry_rptr; }. In that case, if an instance of Bar deduces the reference count by 1, Foo will still have it's reference count recorded, and will not be left with a dangling pointer. This is distinct from the behavior offered by boost::shared_ptr<> (AKA std::tr1::shared_ptr<>.) boost::shared_ptr<> does not cause any direct change in the internal state of the object it owns. Rahter, it relies on direct contact with other boost::shared_ptr<> instances to communicate the ownership state. This is fairly similar to the way std::auto_ptr<> works, with the exception that std::auto_ptr<> is a single ownership handle. The counterpart to the osg::ref_ptr in boost is boost::intrusive_ptr<>. I started working out an approach to using that as the foundation of my shared object management. Unfortunately, it comes with all the same problems as the osg::Referenced, and osg::ref_ptr<>. -- Regards, Steven
participants (2)
-
Stefan Hundhammer
-
Steven T. Hatton