Re: A trick for dealing with functions that take a pointer and return a result via it
Jon Colverson wrote:
Andrei Polushin wrote:
Jon Colverson wrote:
The class works by providing automatic conversion to the required
pointer type,
You are talking about "type conversion", so we can call it "type_cast".
In a sense, but I didn't want to mislead users into thinking that the
conversion was performed by one of the standard language casts. I
thought it was important to mention "assignment", which I why I settled
on "AssignResultTo" for the name.
This was a moot point, you should ask others. I prefer to be mislead.
boost::shared_ptr doesn't work with this class because it doesn't
support assignment from the equivalent raw pointer type. This case can
be dealt with by another class (I called it ResetWithResult) which works
the same way but calls reset(T*) on the destination instead of doing an
assignment.
Yet another pointer type is the case when your template should be
explicitly specialized.
I hadn't thought of this, and it's a nice idea but I'm concerned about
having to hard code in a specialisation for each class that uses this
structure. Since it's used by auto_ptr and shared_ptr I suspect that
there are other smart pointer classes out there that work the same way
and could therefore be used with a templated ResetWithResult.
And I would prefer to implement the most generic referer_cast<> which
will work with auto_ptr/shared_ptr by default, when non-specialized.
As you can see, the class also provides a conversion to a reference for
use with functions that take one of those instead of a pointer.
It will be better to separate reference conversion from pointer
conversion, as it has been done with standard C++ conversions
(static_cast, const_cast, etc.), so the following should be possible:
long a;
GiveMeANumber(referer_cast<int*>(&a)); // pointer cast
GiveMeANumber(&referer_cast<int&>(a)); // reference cast
I call it "referer_cast" to illustrate that we are casting something
that refers to something valuable, i.e. "referer", which can be
either reference, or pointer, or smart pointer.
This makes usage a bit trickier and I'm not yet convinced that it solves
a real problem. Can you be more specific about potential problems doing
it the way I did?
Yes, your implementation is unable to resolve the overload
void GiveMeANumber(int* ptr);
void GiveMeANumber(int& ref);
There's also a second parameter for the constructor which provides a
default value which will be assigned if the result isn't modified by
the function.
That's not a good idea. Initialization is the user responsibility. You
loose performance, but you should care about potential side effects
here: some compilers have runtime checks for the use of uninitialized
variables, and you disable their logic by substituting arbitrary value.
It makes sense to leave it up to the user but in your implementation the
user has no choice but to use the uninitialised value.
It's my bug.
Perhaps it would
be better to provide two constructors as I described later in my first
post so that a default can be provided if the user knows or suspects
that it won't be modified by the called function:
AssignResultTo(DestinationType &d) : destination(&d) {}
AssignResultTo(DestinationType &d, const ResultType &r)
: destination(&d), result(r) {}
This also provides a solution to the (presumably rare) case where
ResultType doesn't have a default constructor.
Another solution is to copy from uninitialized
AssignResultTo(DestinationType &d)
: destination(&d), result(*destination) {}
which contradicts to my previous thought about runtime checks, though.
I'm not sure about this issue of allowing an uninitialised result to be
used. I don't think that the benefits outweigh the drawback of suprising
some users with an uninitialised value when they weren't expecting one.
I'm leaning towards the idea of always initalising by default, but
allowing this to be overridden by defining a preprocessor symbol.
Well, I don't know, too.
It's a shame that the DestinationType can't be deduced from the
constructor argument as that would make the syntax more concise. I tried
to achieve this by making a helper function that just returned an
instance by value but that required making a (destructive) copy
constructor and that made things much harder for the optimisers and led
to a significant performance penalty.
This case is optimized by most compilers, the copy constructor is not
called, but you cannot prohibit it due to logical rules described in
C++ standard 12.2.
I don't have the standard to hand,
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2009.pdf
but I suspect that the copy of this
class couldn't be optimised away because it does something which affects
an external object in the destructor.
It's a well-discussed issue, google this group for "copy constructor".
The destructor is not concerned here, just look how it is optimized;
the code we write:
string GiveMeAString()
{
return "sdf";
}
void f()
{
string s = GiveMeAString();
}
is usually optimized as follows (pseudo C++):
void GiveMeAString(string* ps)
{
new(ps) string("sdf"); // initialized in place
}
void f()
{
string s(?); // uninitialized, constructor not called yet
GiveMeAString(&s); // became initialized after call
}
thus there is no call to copy constructor.
Also, wrybred pointed out a
problem with exception safety in his post. I think this makes it
important to do a destructive copy so that the destination isn't touched
if an exception is thrown during the lifetime of the temporary objects.
Yes, the destructive copy constructor is important, both for logical
reasons and for the weakest optimizers.
--
Andrei Polushin
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]