Re: [boost] pimpl library proposal

On Dec 3, 2010, at 8:26 PM, Phil Endecott wrote:
which really isn't much different in terms of the amount of typing, except for the forwarding. Can you show what your BOOST_PIMPL_METHOD expands to? I can't immediately guess how you manage to not tell it all the argument and return types.
First of all, thanks for your input. I appreciate it. I will post the source below, so as to not clutter the conversation.
Re using the shared_ptr, this mainly gains the safety of otherwise forgetting to implement the dtor, and has speed and space disadvantages. I'm not convinced that is really useful, and so I would decouple it from the concise forwarding feature.
I think that the value comes in avoiding the pitfall of forgetting to free the pointer, as per all smart pointer implementations. That said, you're right in pointing out that shared_ptr just adds overhead. I think a preferable method, after taking in your considerations, is to just have the boost::pimpl<> class manage the allocating and freeing directly with new/delete. This does not add any overhead, but still gives the same safety of shared_ptr. You actually bring up an interesting idea. Perhaps the forwarding should be decoupled (which would not be hard at all) and generalized, rather than pushing the user into the pimpl paradigm. The user could easily use the forwarding for pimpl implementations or for something completely different.
Generally, I think that implementing pimpls falls more into the category of stuff that's best done with some sort of tool, e.g. a "pimpl wizzard" in your IDE, rather than a library.
---- boost/pimpl.hpp ---- #include <boost/typeof/typeof.hpp> #include <boost/type_traits/function_traits.hpp> #include <boost/preprocessor/repetition/enum_trailing_params.hpp> #include <boost/preprocessor/repetition/enum_shifted_params.hpp> #include <boost/preprocessor/repetition/enum_params.hpp> #include <boost/preprocessor/repetition/enum_shifted.hpp> #include <boost/preprocessor/arithmetic/inc.hpp> namespace boost { template <typename T> class pimpl { public: typedef T* ptr_type; pimpl( ptr_type p = new T() ) : m_impl( p ) {} ~pimpl() { delete m_impl; } protected: T& impl() { return *m_impl; } const T& impl() const { return *m_impl; } private: ptr_type m_impl; }; template <typename T> struct method_traits; #ifndef BOOST_METHOD_TRAITS_LIMIT # define BOOST_METHOD_TRAITS_LIMIT 10 #endif #define BOOST_METHOD_TRAITS(z,n,data) \ template <typename C, typename R \ BOOST_PP_ENUM_TRAILING_PARAMS_Z(z,n,class T) > \ struct method_traits< R(C::*)( BOOST_PP_ENUM_PARAMS_Z(z,n,T) ) data > \ : public function_traits< R( BOOST_PP_ENUM_PARAMS_Z(z,n,T) ) > { \ typedef data C class_type; \ }; BOOST_PP_REPEAT(BOOST_METHOD_TRAITS_LIMIT,BOOST_METHOD_TRAITS,) BOOST_PP_REPEAT(BOOST_METHOD_TRAITS_LIMIT,BOOST_METHOD_TRAITS,const) BOOST_PP_REPEAT(BOOST_METHOD_TRAITS_LIMIT,BOOST_METHOD_TRAITS,volatile) BOOST_PP_REPEAT(BOOST_METHOD_TRAITS_LIMIT,BOOST_METHOD_TRAITS,const volatile) } #define BOOST_PIMPL_CTOR( cls ) \ cls::cls() {} #define BOOST_PIMPL_DEFINE_METHOD_M( z, n, data ) \ BOOST_JOIN(BOOST_JOIN(data::arg,n),_type) BOOST_JOIN(a,n) #define BOOST_PIMPL_METHOD( args, cls, meth, ... ) \ ::boost::method_traits< BOOST_TYPEOF(&cls::meth) >::result_type \ cls::meth( \ BOOST_PP_ENUM_SHIFTED( BOOST_PP_INC(args), \ BOOST_PIMPL_DEFINE_METHOD_M, \ ::boost::method_traits<typeof(&cls::meth)> \ ) \ ) __VA_ARGS__ { \ return impl().meth( \ BOOST_PP_ENUM_SHIFTED_PARAMS(BOOST_PP_INC(args),a) \ ); \ }

2010/12/4 Michael Bailey <jinxidoru@gmail.com>
---- boost/pimpl.hpp ----
#include <boost/typeof/typeof.hpp> #include <boost/type_traits/function_traits.hpp> #include <boost/preprocessor/repetition/enum_trailing_params.hpp> #include <boost/preprocessor/repetition/enum_shifted_params.hpp> #include <boost/preprocessor/repetition/enum_params.hpp> #include <boost/preprocessor/repetition/enum_shifted.hpp> #include <boost/preprocessor/arithmetic/inc.hpp>
namespace boost {
template <typename T> class pimpl { public:
typedef T* ptr_type;
pimpl( ptr_type p = new T() ) : m_impl( p ) {}
~pimpl() { delete m_impl; }
protected:
T& impl() { return *m_impl; } const T& impl() const { return *m_impl; }
private: ptr_type m_impl; };
template <typename T> struct method_traits;
#ifndef BOOST_METHOD_TRAITS_LIMIT # define BOOST_METHOD_TRAITS_LIMIT 10 #endif
#define BOOST_METHOD_TRAITS(z,n,data) \ template <typename C, typename R \ BOOST_PP_ENUM_TRAILING_PARAMS_Z(z,n,class T) > \ struct method_traits< R(C::*)( BOOST_PP_ENUM_PARAMS_Z(z,n,T) ) data > \ : public function_traits< R( BOOST_PP_ENUM_PARAMS_Z(z,n,T) ) > { \ typedef data C class_type; \ };
BOOST_PP_REPEAT(BOOST_METHOD_TRAITS_LIMIT,BOOST_METHOD_TRAITS,) BOOST_PP_REPEAT(BOOST_METHOD_TRAITS_LIMIT,BOOST_METHOD_TRAITS,const) BOOST_PP_REPEAT(BOOST_METHOD_TRAITS_LIMIT,BOOST_METHOD_TRAITS,volatile) BOOST_PP_REPEAT(BOOST_METHOD_TRAITS_LIMIT,BOOST_METHOD_TRAITS,const volatile)
}
#define BOOST_PIMPL_CTOR( cls ) \ cls::cls() {}
#define BOOST_PIMPL_DEFINE_METHOD_M( z, n, data ) \ BOOST_JOIN(BOOST_JOIN(data::arg,n),_type) BOOST_JOIN(a,n)
#define BOOST_PIMPL_METHOD( args, cls, meth, ... ) \ ::boost::method_traits< BOOST_TYPEOF(&cls::meth) >::result_type \ cls::meth( \ BOOST_PP_ENUM_SHIFTED( BOOST_PP_INC(args), \ BOOST_PIMPL_DEFINE_METHOD_M, \ ::boost::method_traits<typeof(&cls::meth)> \ ) \ ) __VA_ARGS__ { \ return impl().meth( \ BOOST_PP_ENUM_SHIFTED_PARAMS(BOOST_PP_INC(args),a) \ ); \ }
Hello, I would like to see some pimpl idiom library in boost. I have tackled the problem of simplifying pimpl implementation a while ego. I begun by designing a base class (similar to Your approach, Michael). In the end, however, I came up with a value_ptr class, which is a bit more general, then just for pimpl idiom. I thought to propose it to boost, but I haven't (yet). By now I have used It many times, and I am happy with it. /** * Smart pointer adaptor, that preserves constness of @c element_type, and upon construction * allocates the object using @c new, and forwards constructor arguments. * * It is perfect for the PIMPL idiom. * * @tparam SmartPtr Underlying smart pointer, owning the object. */ template < class SmartPtr, class Element = typename pointee<SmartPtr>::type, class ValuePtrAllocator = value_ptr_heap_allocator<SmartPtr> > class value_ptr; MyClass.h: class MyClass { public: MyClass(); // needed, but trivial ~MyClass(); // sometimes needed, but trivial // methods... private: struct Impl; value_ptr< scoped_ptr<Impl> > impl_; }; MyClass.cpp: struct MyClass::Impl {/*...*/}; MyClass::MyClass() {} // value_ptr automatically allocates an Impl instance MyClass::~MyClass() {} // value_ptr automatically deallocates Impl // methods... So, we gain: - easy allocation (without explicit call to new, which avoids possible problems with exception safety if we need to call some throwing operations in the ctor), - value_ptr has a set of templated ctors, which forward args to Impl's ctor, - allocation is customizable by value_ptr's third template parameter, - automatic deallocation, which is done by the underlying SmartPtr (scoped_ptr, shared_ptr, or some other ptr like clone_ptr can be used), - const-correct versions of operator* and -> with respect to the fact, that MyClass logically owns it's Impl. Right now I can't spend more time on trying to understand how the method macros proposed by Michael work (I'm not familiar with advanced macros), but I like the Idea, and I am looking forward to adding those to value_ptr in the future ;-) The source code for value_ptr together with a few other related tools can be found here: https://libcz.svn.sourceforge.net/svnroot/libcz/trunk/boost Regards, Kris

See if the pimpl proposal currently sitting in the review queue http://www.boost.org/community/review_schedule.html meets your needs. If not, let's extend it. Best, V. ----- Original Message ----- From: "Krzysztof Czainski" <1czajnik@gmail.com> Newsgroups: gmane.comp.lib.boost.devel To: <boost@lists.boost.org> Sent: Monday, December 06, 2010 5:25 AM Subject: Re: pimpl library proposal
2010/12/4 Michael Bailey <jinxidoru@gmail.com>
---- boost/pimpl.hpp ----
#include <boost/typeof/typeof.hpp> #include <boost/type_traits/function_traits.hpp> #include <boost/preprocessor/repetition/enum_trailing_params.hpp> #include <boost/preprocessor/repetition/enum_shifted_params.hpp> #include <boost/preprocessor/repetition/enum_params.hpp> #include <boost/preprocessor/repetition/enum_shifted.hpp> #include <boost/preprocessor/arithmetic/inc.hpp>
namespace boost {
template <typename T> class pimpl { public:
typedef T* ptr_type;
pimpl( ptr_type p = new T() ) : m_impl( p ) {}
~pimpl() { delete m_impl; }
protected:
T& impl() { return *m_impl; } const T& impl() const { return *m_impl; }
private: ptr_type m_impl; };
template <typename T> struct method_traits;
#ifndef BOOST_METHOD_TRAITS_LIMIT # define BOOST_METHOD_TRAITS_LIMIT 10 #endif
#define BOOST_METHOD_TRAITS(z,n,data) \ template <typename C, typename R \ BOOST_PP_ENUM_TRAILING_PARAMS_Z(z,n,class T) > \ struct method_traits< R(C::*)( BOOST_PP_ENUM_PARAMS_Z(z,n,T) ) data > \ : public function_traits< R( BOOST_PP_ENUM_PARAMS_Z(z,n,T) ) > { \ typedef data C class_type; \ };
BOOST_PP_REPEAT(BOOST_METHOD_TRAITS_LIMIT,BOOST_METHOD_TRAITS,) BOOST_PP_REPEAT(BOOST_METHOD_TRAITS_LIMIT,BOOST_METHOD_TRAITS,const) BOOST_PP_REPEAT(BOOST_METHOD_TRAITS_LIMIT,BOOST_METHOD_TRAITS,volatile) BOOST_PP_REPEAT(BOOST_METHOD_TRAITS_LIMIT,BOOST_METHOD_TRAITS,const volatile)
}
#define BOOST_PIMPL_CTOR( cls ) \ cls::cls() {}
#define BOOST_PIMPL_DEFINE_METHOD_M( z, n, data ) \ BOOST_JOIN(BOOST_JOIN(data::arg,n),_type) BOOST_JOIN(a,n)
#define BOOST_PIMPL_METHOD( args, cls, meth, ... ) \ ::boost::method_traits< BOOST_TYPEOF(&cls::meth) >::result_type \ cls::meth( \ BOOST_PP_ENUM_SHIFTED( BOOST_PP_INC(args), \ BOOST_PIMPL_DEFINE_METHOD_M, \ ::boost::method_traits<typeof(&cls::meth)> \ ) \ ) __VA_ARGS__ { \ return impl().meth( \ BOOST_PP_ENUM_SHIFTED_PARAMS(BOOST_PP_INC(args),a) \ ); \ }
Hello,
I would like to see some pimpl idiom library in boost. I have tackled the problem of simplifying pimpl implementation a while ego. I begun by designing a base class (similar to Your approach, Michael). In the end, however, I came up with a value_ptr class, which is a bit more general, then just for pimpl idiom. I thought to propose it to boost, but I haven't (yet). By now I have used It many times, and I am happy with it.
/** * Smart pointer adaptor, that preserves constness of @c element_type, and upon construction * allocates the object using @c new, and forwards constructor arguments. * * It is perfect for the PIMPL idiom. * * @tparam SmartPtr Underlying smart pointer, owning the object. */ template < class SmartPtr, class Element = typename pointee<SmartPtr>::type, class ValuePtrAllocator = value_ptr_heap_allocator<SmartPtr> > class value_ptr;
MyClass.h:
class MyClass { public: MyClass(); // needed, but trivial ~MyClass(); // sometimes needed, but trivial // methods... private: struct Impl; value_ptr< scoped_ptr<Impl> > impl_; };
MyClass.cpp:
struct MyClass::Impl {/*...*/};
MyClass::MyClass() {} // value_ptr automatically allocates an Impl instance
MyClass::~MyClass() {} // value_ptr automatically deallocates Impl
// methods...
So, we gain: - easy allocation (without explicit call to new, which avoids possible problems with exception safety if we need to call some throwing operations in the ctor), - value_ptr has a set of templated ctors, which forward args to Impl's ctor, - allocation is customizable by value_ptr's third template parameter, - automatic deallocation, which is done by the underlying SmartPtr (scoped_ptr, shared_ptr, or some other ptr like clone_ptr can be used), - const-correct versions of operator* and -> with respect to the fact, that MyClass logically owns it's Impl.
Right now I can't spend more time on trying to understand how the method macros proposed by Michael work (I'm not familiar with advanced macros), but I like the Idea, and I am looking forward to adding those to value_ptr in the future ;-)
The source code for value_ptr together with a few other related tools can be found here: https://libcz.svn.sourceforge.net/svnroot/libcz/trunk/boost
Regards, Kris _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

2010/12/7 Vladimir Batov <vbatov@people.net.au>
See if the pimpl proposal currently sitting in the review queue
Hello Vladimir, I took a look at Your pimpl code. I'm afraid it doesn't look like something I'm looking for. Here is a list of my expectations of pimpl. Suppose the implementer is preparing a class MyClass. A user is someone using MyClass. 1. Pimpl should hide the implementation. Your pimpl does that well. 2. Pimpl should automate construction and destruction of the Impl struct. Your pimpl does that, but lacks a way of customization of the copying policy. And perhaps a way of customization of the creating policy, a factory of some sort... 3. The user should have no way of telling if MyClass uses pimpl or not. It should be totally transparent. With Your pimpl, MyClass inherits from pimpl_base, bringing in a lot of operations: operator*(), operator bool_type() and others. They are protected, but they still are there - potentially conflicting with operators, that MyClass might be providing for the user. Therefore I'd prefere making pimpl a member over inheriting from it. 4. Customizability of pimpl: the implementer might want one of different approaches to copying: a) disallow, b) share, c) clone, d) others like COW. I'm not sure which of these Your pimpl provides. I would recommend a template parameter to choose a policy. It might be just a smart pointer owning the Impl object. 5. Pimpl should not do more, then there is to the pimpl idiom. That is, it should hide the implementation. But it should not implement features like operator !=, when the implementer might not want those. Library operators is for that. Or it should not implement the possibility of a null object - boost::optional is for that. 6. You write about pimpl and dynamic polymorphism. I think pimpl should have nothing to do with polymorphism. Any polymorphic part of a class, even a private virtual function, isn't truly private, so it shouldn't be in pimpl. And everything that is in a pimpl should be truly hidden, even from the derived classes. Te derived class should not be able to tell if MyClass even uses pimpl or not. And the derived class might as well have it's own pimpl. See if the pimpl proposal currently sitting in the review queue meets your
needs. If not, let's extend it.
Best, V.
Well, in my opinion, the pimpl You propose does too much things, that I wouldn't want in it. They might be useful sometimes, but not always. But that opinion might be based on the fact, that I understand the pimpl idiom differently. I use the approach similar to Herb Sutter's: http://www.gotw.ca/gotw/024.htm I think Your pimpl lacks a good enough way to customize the copying and creation policies, but I suppose You wouldn't have anything against adding that... Regards, Kris

Kris, Thanks for your input. It's much appreciated.
From: "Krzysztof Czainski" <1czajnik@gmail.com> ... 2. Pimpl should automate construction and destruction of the Impl struct. Your pimpl does that, but lacks a way of customization of the copying policy.
Have you had a chance to read through the Pimpl documentation? Currently, the described Pimpl provides two policies -- pointer_semantics and value_semantics. In the pimpl.hpp code it looks as follows: template<class T> struct pimpl { typedef pimpl_base<impl_ptr> value_semantics; typedef pimpl_base<boost::shared_ptr> pointer_semantics; }; More so, nothing is stopping us from extending it with, say, COW semantics: template<class T> struct pimpl { typedef pimpl_base<impl_ptr> value_semantics; typedef pimpl_base<boost::shared_ptr> pointer_semantics; typedef pimpl_base<cow_ptr> cow_semantics; }; Isn't it "customization of the copying policy"? Or did you mean something else?
And perhaps a way of customization of the creating policy, a factory of some sort...
I am under impression that I do not quite understand what you mean by "customization of the creating policy" either. Isn't that quality achieved by overloaded constructors? If it is so, then the developer provides the set of overloaded constructors for his/her class. Regardsless of those constructors Pimpl does not need to be modified.
3. The user should have no way of telling if MyClass uses pimpl or not. It should be totally transparent.
I find it hard to visualize what you have in mind. Would it be possible to provide an example where you believe manually-written pimpl-idiom class is more transparent than a class derived from the proposed pimpl?
With Your pimpl, MyClass inherits from pimpl_base, bringing in a lot of operations: operator*(), operator bool_type() and others. They are protected, but they still are there - potentially conflicting with operators, that MyClass might be providing for the user. Therefore I'd prefere making pimpl a member over inheriting from it.
Well, you omitted concrete examples/explanations clarifying why you prefer composition over inheritance in this particular case. Consequently, I have that nagging feeling that you are taking the "prefer composition over inheritance" a tiny bit too dogmatically. From where I stand I feel that Pimpl-derived class needs/wants to expose the complete interface of Pimpl (after all that's why those methods were introduced in Pimpl -- to re-use them). That indicates inheritance. In my view Liskov Substitution Principle is a perfect litmus test for "should I be inheriting from this type?". And in my view Pimpl fits the bill. After all, functional inheritance (as opposed to architectural inheritance) is quite common programming technique. As for your concern about "a lot of operations" in Pimpl "potentially conflicting with operators" of YourClass, then my reading of the specification is somewhat different. Namely, as I understand, a method introduced in the derived class makes *all* the methods with the same name (not just the same signature) in the base class inaccessible.
4. Customizability of pimpl: the implementer might want one of different approaches to copying: a) disallow, b) share, c) clone, d) others like COW. I'm not sure which of these Your pimpl provides. I would recommend a template parameter to choose a policy. It might be just a smart pointer owning the Impl object.
I hope I covered that while answering your #2 point.
5. Pimpl should not do more, then there is to the pimpl idiom. That is, it should hide the implementation. But it should not implement features like operator !=, when the implementer might not want those.
Again, I feel you should have another closer look at the suggested implementation... because the proposed Pimpl implements (or not) operator==() based on the supplied policy. Namely, I do not see why a pointer_semantics-based Pimpl should not provide op==() automatically (as shared_ptr does). On the other hand, the user has to provide op==() for a value_semantics-based Pimpl.
Or it should not implement the possibility of a null object - boost::optional is for that.
Well, my view on the usefullness of pimpl::null() happens to be quite different. In fact, that pimpl::null is there because I use it a *lot*. Pimpl belongs to a smart-pointer-pattern family and shared_ptr and the likes provide such functionality (in a somewhat obscure form of shared_ptr() IMHO of cource).
6. You write about pimpl and dynamic polymorphism. I think pimpl should have nothing to do with polymorphism. Any polymorphic part of a class, even a private virtual function, isn't truly private, so it shouldn't be in pimpl. And everything that is in a pimpl should be truly hidden, even from the derived classes.
Well, "pimpl should have nothing to do with polymorphism" is quite a determined statement. ;-) That mentioned functionality has its purpose -- it implements Non-Virtual Interface Idiom. If you have no need for that, that's OK. We should not be denying it to others, should we? In fact, I happen to use it to fight the complexities of architectural inheritance (where it's unavoidable). Best, V.

2010/12/9 Vladimir Batov <vbatov@people.net.au>
Kris,
Thanks for your input. It's much appreciated.
Have you had a chance to read through the Pimpl documentation? Currently, the described Pimpl provides two policies -- pointer_semantics and value_semantics. In the pimpl.hpp code it looks as follows:
Yes, I have read that documentation ;-)
template<class T> struct pimpl { typedef pimpl_base<impl_ptr> value_semantics; typedef pimpl_base<boost::shared_ptr> pointer_semantics; };
More so, nothing is stopping us from extending it with, say, COW semantics:
template<class T> struct pimpl { typedef pimpl_base<impl_ptr> value_semantics; typedef pimpl_base<boost::shared_ptr> pointer_semantics; typedef pimpl_base<cow_ptr> cow_semantics; };
Isn't it "customization of the copying policy"? Or did you mean something else?
I see. But if I understand correctly, for each smart pointer type, there needs to be a specialization of pimpl_base written from scratch. Assuming that a SmartPtr provides pointer operators and performs copying according to some policy, You might consider writing a general pimpl_base<SmartPtr>. Now suppose pimpl_base<SmartPtr> does accept any smart pointer: scoped_ptr, shared_ptr, clone_ptr, cow_ptr, etc... This is where the creation policy is needed. See below... And perhaps a way of customization of the creating policy, a factory
of some sort...
I am under impression that I do not quite understand what you mean by "customization of the creating policy" either. Isn't that quality achieved by overloaded constructors? If it is so, then the developer provides the set of overloaded constructors for his/her class. Regardsless of those constructors Pimpl does not need to be modified.
The default creation policy would be to create impl on the heap using operator new, and passing the resulting pointer to SmartPtr. But for example, when using shared_ptr, it might be a good idea to use a) make_shared instead of operator new, b) a custom allocator/deleter. Thus, I suggest making pimpl_base< SmartPtr, Factory = HeapFactory<SmartPtr>
a general class with no specializations per SmartPtr.
3. The user should have no way of telling if MyClass uses pimpl or not. It
should be totally transparent.
I find it hard to visualize what you have in mind. Would it be possible to
provide an example where you believe manually-written pimpl-idiom class is more transparent than a class derived from the proposed pimpl?
Well, I understand You expose the operator bool_type for example. But that is not all. I believe a user is also someone deriving form MyClass. Your pimpl takes part in runtime polymprphism, and for that it mus be visible by derived classes.
With Your pimpl, MyClass inherits from
pimpl_base, bringing in a lot of operations: operator*(), operator bool_type() and others. They are protected, but they still are there - potentially conflicting with operators, that MyClass might be providing for the user. Therefore I'd prefere making pimpl a member over inheriting from it.
Well, you omitted concrete examples/explanations clarifying why you prefer composition over inheritance in this particular case. Consequently, I have that nagging feeling that you are taking the "prefer composition over inheritance" a tiny bit too dogmatically. From where I stand I feel that Pimpl-derived class needs/wants to expose the complete interface of Pimpl (after all that's why those methods were introduced in Pimpl -- to re-use them). That indicates inheritance.
In my view Liskov Substitution Principle is a perfect litmus test for "should I be inheriting from this type?". And in my view Pimpl fits the bill.
I just thought that, since the user should not know of pimpl's existence, there should be no functions to inherit from it. After all, functional inheritance (as opposed to architectural inheritance)
is quite common programming technique.
As for your concern about "a lot of operations" in Pimpl "potentially conflicting with operators" of YourClass, then my reading of the specification is somewhat different. Namely, as I understand, a method introduced in the derived class makes *all* the methods with the same name (not just the same signature) in the base class inaccessible.
True. So, what if the implementer adds operators * and -> to MyClass? Accessing pimpl is still possible, but not as convenient any more. satic_cast<implementation&>(*this)->... or an extra local reference is always needed: void MyClass::f() { implementation& impl = *this; impl->... }
Namely, I do not see why a pointer_semantics-based Pimpl should not provide op==() automatically (as shared_ptr does). On the other hand, the user has to provide op==() for a value_semantics-based Pimpl.
For the pointer_semantics-based Pimpl: I do not think that MyClass should have pointer semantics just because its pimpl has pointer semantics. So I am against forwarding op== from the underlying shared_ptr. For the value_semantics-based Pimpl: If the implementer wants op== in MyClass, he should provide it. And impl should only contain data, and perhaps helper functions, that would otherwise be private nonvirtual member functions of MyClass without pimpl. Or it should not implement the possibility of a null object -
boost::optional is for that.
Well, my view on the usefullness of pimpl::null() happens to be quite
different. In fact, that pimpl::null is there because I use it a *lot*. Pimpl belongs to a smart-pointer-pattern family and shared_ptr and the likes provide such functionality (in a somewhat obscure form of shared_ptr() IMHO of cource).
On second thought, I agree, null() might come in handy, as long as there is a way to disable it if it isn't needed. Well, "pimpl should have nothing to do with polymorphism" is quite a
determined statement. ;-) That mentioned functionality has its purpose -- it implements Non-Virtual Interface Idiom. If you have no need for that, that's OK. We should not be denying it to others, should we? In fact, I happen to use it to fight the complexities of architectural inheritance (where it's unavoidable).
I just think virtual functions should just stay in MyClass, and not go into pimpl, because they should not be hidden. That is why I think "pimpl should have nothing to do with polymorphism" ;-) Thanks for the interesting discussion, Vladimir ;-) For an example of how I use pimpl, and my pimpl helper, which I called value_ptr, I shall quote my earlier post, adding an example of how some member function of MyClass might be implemented in terms of pimpl: MyClass.h: class MyClass { public: MyClass(); // needed, but trivial ~MyClass(); // sometimes needed, but trivial void f(); private: struct Impl; value_ptr< scoped_ptr<Impl> > impl_; }; MyClass.cpp: struct MyClass::Impl { /*...*/ void f(int) {} }; MyClass::MyClass() {} // value_ptr automatically allocates an Impl instance MyClass::~MyClass() {} // value_ptr automatically deallocates Impl void MyClass::f() { if ( cond ) impl_->f(1); else impl_->f(10); } The source code for value_ptr together with a few other related tools can be found here: https://libcz.svn.sourceforge.net/svnroot/libcz/trunk/boost Regards, Kris

From: "Krzysztof Czainski" <1czajnik@gmail.com> Sent: Friday, December 10, 2010 12:06 AM ...
template<class T> struct pimpl { typedef pimpl_base<impl_ptr> value_semantics; typedef pimpl_base<boost::shared_ptr> pointer_semantics; typedef pimpl_base<cow_ptr> cow_semantics; };
Isn't it "customization of the copying policy"? Or did you mean something else?
I see. But if I understand correctly, for each smart pointer type, there needs to be a specialization of pimpl_base written from scratch.
I am not sure where you've got that impression from. There is one pimpl_base implementation. Pointer- or value-semantics behavior are defined by the provided policy class. Pls see the declaration above.
Now suppose pimpl_base<SmartPtr> does accept any smart pointer: scoped_ptr, shared_ptr, clone_ptr, cow_ptr, etc... This is where the creation policy is needed. See below...
I do not immediately see value in Pimpl accepting "any smart pointer" which are policy classes for Pimpl. In fact, I do not know of any policy-based class which would accept *any* other class as its policy. I am convinced a class to be used as a policy by another class must conform to a certain interface.
...
I am under impression that I do not quite understand what you mean by "customization of the creating policy" either. Isn't that quality achieved by overloaded constructors? If it is so, then the developer provides the set of overloaded constructors for his/her class. Regardsless of those constructors Pimpl does not need to be modified.
The default creation policy would be to create impl on the heap using operator new, and passing the resulting pointer to SmartPtr. But for example, when using shared_ptr, it might be a good idea to use a) make_shared instead of operator new, b) a custom allocator/deleter.
I am under impression that the functionality you are suggesting is already available. Namely, (unless I am still missing your point) "make_shared" quality is provided by boost::shared_ptr itself and nothing more needs to be done. As for "custom allocator/deleter" or any other creation policy, then that functionality belongs in the "implementation" class and deployed via Pimpl interface. So, it seems nothing more needs to be done in Pimpl either.
3. The user should have no way of telling if MyClass uses pimpl or not. It should be totally transparent.
I happen to disagree. Pimpl implements a certain pattern/idiom which has certain qualities. The user deploying that pattern/idiom expects those qualities. After all, that's why he/she is deploying that pattern/idiom in the first place. I do not immediately see benefits of hiding those qualities.
Well, I understand You expose the operator bool_type for example.
Again, I believe it is part of the idiom. For example, boost::shared_ptr is similar in that regard.
But that is not all. I believe a user is also someone deriving form MyClass. Your pimpl takes part in runtime polymprphism, and for that it must be visible by derived classes.
Again, Pimpl is implementation of a certain programming technique. Consequently, one can certainly inherit from a Pimpl (and I do it a lot) but without negating Pimpl's property and ultimately the object size. It's done via the Bridge pattern (as in GoF). Essentially, the developer has two separate inheritance hierarchies -- for interface and for implementation. I believe I have it covered in the documentation.
... I just thought that, since the user should not know of pimpl's existence, there should be no functions to inherit from it.
I see no value in encapsulation for the sake of encapsulation. Say, you can "typedef boost::shared_ptr<Foo> Zoo;" or go further trying to hide the fact that Zoo is, in fact, a shared_ptr and, consequently behaves as a shared_ptr. By trying to hide that fact I believe you do the user great disservice as you deny the user the knowledge that Zoo has certain qualities/properties (namely, shared_ptr properties) as *must* be treated as such.
...
As for your concern about "a lot of operations" in Pimpl "potentially conflicting with operators" of YourClass, then my reading of the specification is somewhat different. Namely, as I understand, a method introduced in the derived class makes *all* the methods with the same name (not just the same signature) in the base class inaccessible.
True. So, what if the implementer adds operators * and -> to MyClass? Accessing pimpl is still possible, but not as convenient any more. satic_cast<implementation&>(*this)->... or an extra local reference is always needed:
Therefore, if I understand you correctly you are suggesting to remove those operators altogether, right? ;-) More so, there is no need for the user to add operators * and -> as Pimpl provides already those operators for him/her.
For the pointer_semantics-based Pimpl: I do not think that MyClass should have pointer semantics just because its pimpl has pointer semantics. So I am against forwarding op== from the underlying shared_ptr.
I am not sure I follow -- you deploy pointer-semantics Pimpl but do not want it to behave as such. I have that feeling you are trying to make things harder than they need be. In my simple world, if I deploy a fork, I do want it to "behave" as a fork, i.e. to expose fork-like properties. If I don't, I'll deploy something else.
I just think virtual functions should just stay in MyClass, and not go into pimpl, because they should not be hidden. That is why I think "pimpl should have nothing to do with polymorphism" ;-)
I do not believe the proposed Pimpl implementation has any virtual functions. As described in the documentation it's deployed in the inheritance hierarchies differently (the Bridge pattern in GoF).
For an example of how I use pimpl, and my pimpl helper, which I called value_ptr, I shall quote my earlier post, adding an example of how some member function of MyClass might be implemented in terms of pimpl:
I am sure your Pimpl implementation does everything just the way you want it. :-) And if that works better for you than an already available component, then I do not see anything wrong with deploying it. I do the same all the time. ;-) Still, I suspect if you try deploying your no-frills version of a larger scale, it'll start looking more and more as the proposed Pimpl. There are countless examples of that behavioral pattern (Java comes to mind). As for the proposed Pimpl, let me assure you that the functionality provided and described there is there for a reason. May I suggest you have another look at the documentation for the proposed Pimpl? You might come to appreciate its versatility... or might not. :-) Good luck and all the best in your endeavors, V.

2010/12/14 Vladimir Batov <vbatov@people.net.au>
I see. But if I understand correctly, for each smart pointer type, there
needs to be a specialization of pimpl_base written from scratch.
I am not sure where you've got that impression from. There is one pimpl_base implementation. Pointer- or value-semantics behavior are defined by the provided policy class. Pls see the declaration above.
You are right, I misunderstood the definition of pimpl_base. I thought it was being specialized outside of pimpl, but it is not the case. template<class T> struct pimpl /// Generalization of the Pimpl idiom { struct implementation; template<class> class impl_ptr; template<template<class> class> class pimpl_base; /// Convenience typedef to deploy pimpl with value semantics. typedef pimpl_base<impl_ptr> value_semantics; /// Convenience typedef to deploy pimpl with pointer semantics. typedef pimpl_base<boost::shared_ptr> pointer_semantics; }; So pimpl_base can accept as its template parameter only a template that takes one argument, like shared_ptr. But it cannot take a template with two or more parameters, correct? Why not just do the following? template < class SmartPtr > class pimpl_base; Another question: what is the purpose of Your pimpl struct? In other words, assuming pimpl_base takes a regular template parameter, what is the advantage of class MyClass : pimpl<MyClass>::pointer_semantics; over class MyClass : pimpl_base< shared_ptr<MyClassImpl> >; Now suppose pimpl_base<SmartPtr> does accept any smart pointer: scoped_ptr,
shared_ptr, clone_ptr, cow_ptr, etc... This is where the creation policy is needed. See below...
I do not immediately see value in Pimpl accepting "any smart pointer" which
are policy classes for Pimpl. In fact, I do not know of any policy-based class which would accept *any* other class as its policy. I am convinced a class to be used as a policy by another class must conform to a certain interface.
I admit "any" is not the right word for what I had in mind. I ment some smart pointer, that the author of the pimpl<> template does not know of; that smart pointer may not yet exist. Since You place typedefs inside pimpl (value_semantics, pointer_semantics), I presume You intend to predict all possible used spart pointers. Otherwise usage would be something like: class MyClass : pimpl<MyClass>::pimpl_base<MySmartPtr>; whereas MySmartPtr would have to be a template with one template argument. ...
I am under impression that I do not quite understand what you mean by "customization of the creating policy" either. Isn't that quality achieved by overloaded constructors? If it is so, then the developer provides the set of overloaded constructors for his/her class. Regardsless of those constructors Pimpl does not need to be modified.
The default creation policy would be to create impl on the heap using operator new, and passing the resulting pointer to SmartPtr. But for example, when using shared_ptr, it might be a good idea to use a) make_shared instead of operator new, b) a custom allocator/deleter.
I am under impression that the functionality you are suggesting is already
available.
I can't find it
Namely, (unless I am still missing your point) "make_shared" quality is provided by boost::shared_ptr itself and nothing more needs to be done.
I disagree. Make_shared is provided separatly from shared_ptr, and must be used instead of operator new, when creating an object.
As for "custom allocator/deleter" or any other creation policy, then that functionality belongs in the "implementation" class and deployed via Pimpl interface. So, it seems nothing more needs to be done in Pimpl either.
A constructor from pimpl_base: pimpl_base() : impl_(new implementation()) {} It creates implementation using operator new. I was suggesting adding a factory parameter to pimpl, and delegating creating to the factory, instead of explicitly calling new implementation()
3. The user should have no way of telling if MyClass uses pimpl or not. It
should be totally transparent.
I happen to disagree. Pimpl implements a certain pattern/idiom which has certain qualities. The user deploying that pattern/idiom expects those qualities. After all, that's why he/she is deploying that pattern/idiom in the first place. I do not immediately see benefits of hiding those qualities.
I think we don't understand each other on the definigion of "user". Earlier I proposed to call a user someone who uses MyClass, and to call an implementer someone, who implements MyClass and optionally uses pimpl. When You say "user deploying [pimpl] pattern/idiom", You must mean the implementer. However, if You don't think hiding the use of pimpl from the user is a good thing, then I guess we just have different oppinions about it. Well, I understand You expose the operator bool_type for example.
Again, I believe it is part of the idiom. For example, boost::shared_ptr is similar in that regard.
I see, so we disagree on this. I don't think exposing operator bool_type is part of the idiom. I think we did not agree on the definition of pimpl, and I think this leads to all the other things we disagree upon ;-) [...] I am sure your Pimpl implementation does everything just the way you want
it. :-) And if that works better for you than an already available component, then I do not see anything wrong with deploying it. I do the same all the time. ;-) Still, I suspect if you try deploying your no-frills version of a larger scale, it'll start looking more and more as the proposed Pimpl. There are countless examples of that behavioral pattern (Java comes to mind). As for the proposed Pimpl, let me assure you that the functionality provided and described there is there for a reason. May I suggest you have another look at the documentation for the proposed Pimpl? You might come to appreciate its versatility... or might not. :-)
Good luck and all the best in your endeavors,
Good luck and all the best to You too, Kris
participants (3)
-
Krzysztof Czainski
-
Michael Bailey
-
Vladimir Batov