Re: Scoped pointer with custom deallocator
On 9 Jul., 07:43, Roman Werpachowski
<roman_dot_werpachowski@gmail_dot_com.org> wrote:
I am writing some C++ code which uses the wonderful FFTW library
(http://www.fftw.org), which is written in C. To avoid memory leaks, I
want to wrap the objects FFTW allocates in C++ classes. I cannot use the
standard boost smart pointers, because (as far as I understand the docs)
they use delete/delete[], while FFTW provides its own
allocation/deallocation routines.
It was a good idea to consider using an existing implementation
of a smart pointer, but you gave up a bit too early ;-)
Specifically you can read in
http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/shared_ptr.htm
that a user-provided deleter can be provided to the constructor
(Search for template parameter D in the specs). I usually
recommend not to reinvent the wheel because there are
several issues one can do easily wrong with smart
pointer implementations, usually involving proper copy
semantics (or lack thereof).
Now, after this praise to reuse existing components I
agree that you cannot directly use boost::shared_ptr ;-)
The proper reason is *not* the lack of the provision of a
customized deleter as you assumed (this is possible as
I have shown above), but the fact that boost::shared_ptr
only accepts *real* pointers and your fftw_plan type
seems not to be one (if it is, we stop here and you should
use the actual pointer that corresponds to fftw_plan).
Note that the currently developed standard C++0x
suggests a library component std::unique_ptr that would
indeed support your use-case, because you would be
able to provide a customized *pointer* as well as a
customized *deleter*. Currently, an implementation
of unique_ptr is not yet provided by boost (It would also
heavily rely on new compilers that know what an rvalue
reference is).
With this in your mind, let's go back to your actual
problem:
At least for arrays, the allocation may
differ from the C++ library mechanism, so I decided to play it safe and
do the allocation/deallocation via calls to FFTW. This led me to
implementing my own very poor scoped pointers, for arrays and single
objects (so-called "FFTW plans"). I have some trouble with the second
class:
template <typename Ptr, typename Deallocator>
class CustomScopedPtr
{
public:
// ptr is the pointer and d is called to deallocate the pointer
CustomScopedPtr(Ptr ptr, Deallocator d_);
~CustomScopedPtr();
Ptr get() const;
private:
Ptr ptr_;
Deallocator& deallocator_;
};
What is the reason that you hold the Deallocator
as a reference member? Note that you cannot
reasonably define the constructor
CustomScopedPtr(Ptr ptr, Deallocator d_)
without an explicit requirement that
Deallocator itself is a reference type.
Your code below takes advantage of
this specialty, but this is far from being
obvious.
Note particularly that your implementation violates
the "rule of three", because you did neither declare
the copy constructor nor the copy assignment
operator (which are the two other from the holy
three special members). While the reference
member does make the implicit generation of
the copy assignment operator ill-formed, it would
not exclude the generation of the copy constructor,
so you could easily construct two objects of
CustomScopedPtr pointing to the same Ptr
and thus deleting the same entity twice.
template <typename Ptr, typename Deallocator>
CustomScopedPtr<Ptr, Deallocator>::CustomScopedPtr(Ptr ptr, Deallocator&
d)
: ptr_(ptr), deallocator_(d)
{
}
Oops, ok here the deallocator is provided by reference.
template <typename Ptr, typename Deallocator>
CustomScopedPtr<Ptr, Deallocator>::~CustomScopedPtr()
{
deallocator_(ptr_);
}
template <typename Ptr, typename Deallocator>
Ptr CustomScopedPtr<Ptr, Deallocator>::get() const
{
return ptr_;
}
I want to use the second class to wrap around the following sequence of C
code:
p = fftw_plan_dft_1d([...]); // parameters not important here
/* do FFT stuff */
fftw_destroy_plan(p);
p is a pointer to some internal FFTW data structure, allocated by
fftw_plan_dft_1d and deallocated by fftw_destroy_plan. I have managed to
store p in CustomScopedPtr like this:
CustomScopedPtr<fftw_plan, void (&)(fftw_plan)> plan( fftw_plan_dft_1d
([...]), fftw_destroy_plan );
It works when I use a function object.
This sentence is not very clear, but I assume (which
you don't say), that your compiler rejects the initialization
of your void (&)(fftw_plan) member with the value of
fftw_destroy_plan. Is this right?
If so, the reason is due to the fact, that fftw_destroy_plan
has extern "C" linkage and your compiler does not accept
that this type is convertible to your extern "C++" void (&)
(fftw_plan)
member (according to the language rules both language
linkages are different). To fix this, you need to use a proper
linkage specification like so:
extern "C" typedef void fftw_dealloc_t(fftw_plan);
Given this typedef, the following should be well-formed:
CustomScopedPtr<fftw_plan, &fftw_dealloc_t> plan(
fftw_plan_dft_1d([...]), fftw_destroy_plan );
Could this approach be somehow
improved? did I just reimplement some well-known library feature?
First I suggest to ensure that you solve the "copyability"
issues of your smart pointer class. It looks as if you
want to provide a non-copyable one, so in this case you
should declare (but not define) the copy constructor with
a private access specifier. While this is a must, I
recommend to declare the copy assignment operator
similarly as a private member without definition. You can
also simply derive your smart pointer class from
boost::noncopyable which is also a good way to
document your intention.
Second, if you smart pointer should be a general purpose
smart pointer, the fact that the deleter has to be provided
by reference is very untypical and non-intuitive. While it
prevents that a user provides a null pointer value for a
function pointer (good!), it does not prevent that someone
provides a reference to an deallocator that has a shorter
life-time than the smart pointer itself (bad!). In general, it's
far more easier to accept the deallocator by value
and to catch the possibility that this value may be null
with a runtime assertion.
HTH & Greetings from Bremen,
Daniel Kr?gler
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]