James said:
I think so, but I think the point is vague enough that I'd avoid
counting on it. The usual rule is to separate destruction and
deallocation anytime you've separated allocation and
initialization. In other words, you sould probably replace 3
with:
p->~T() ;
:
perator delete( p ) ;
That doesn't work the way I do things (explanation below).
I'm not sure I understand this. If the object is on the stack,
it will have been constructed where it was declared, and will be
destructed when it goes out of scope. So you can't use
placement new on it later, and you can't explicitly delete it
in any way.
But it exists in a possible call algorithm. So I will give more details
about the code.
Supose I have a hierarchy of user defined types, DerivedX (with X being from
1 to 3) inherited from Base. The contents of a DerivedX at some point it is
serialized somehow (say by first storing some kind of numeric value that
associates the type stored and then the contents of the type). When I do
deserialization obviously I need to read that value and then deserialize
the actual type that I know follows in the input stream by using a ctor of
the deserialized type that receives various arguments which arguments are
first deserialized. I cannot just make a ctor version for each
deserializable type that takes the stream type as argument and it will
construct by deserialization from the stream and I cannot do something like
overloading some operator<< on my stream types because there are many ways
the objects are serialized/deserialized and they don't all depend on the
stream type used (or making such stream types for each way would be
pointless). So each way to deserialize has an API that will do it and
considering one such way/API it should be able to provide 2 ways to do it:
- one way, more straightforward and OOP (especially since all are inherited
from a common base) is to have something like this:
auto_ptr<Node> load(InputStream& is);
Which obviously will read the type, will switch/case on it and then will
deserialize the arguments needed for the ctor and then do something like
return auto_ptr<Node>(new DerivedX(args...)); (where X will be dependent on
the read numeric value that associates the serialized type)
While fine from an OOP perspective this code fails in many other aspects
because it will "lose" the actual type it was dereferenced and will return
a (smart) pointer to the Base class thus forcing the callers to use some
method (say a Visitor aproach) to identify the type again and perform
actual work on the specific type. So then I said, I should have another API
that does not lose the type.
- the second way to perform object deserialization is to have a load like
this:
template<typename Function>
void load(InputStream& is, Function func);
In this second case, "load" will read again (from the stream) the numeric
value that identifies the serialized type, then again will deserialize the
arguments specific to this type and then will create locally (on stack) an
object of the specific type using the deserialized arguments and will call
func(thatobject) (so Function needs to have an operator() taking each
possible obeject type). This is like a callback algorithm but avoiding the
costs of using boost::function (or other such generic functor) because the
callback happens with normal function call resolved at compile time and
will also avoid a costly Visitor aproach to get the type lost from the
first "load" version.
In both "load" versions I need to read the numeric value, switch/case on it
and then deserialize the arguments specific to the type and then construct
the type from those arguments. Using the original question method I could
decouple the reading of arguments/construction from the actual object
storage without having the functions that do the actual read be templates
(which I want to avoid) but they could just be something like:
Derived0*
loadDerived0(InputStream& is, void* where) {
// deserialize specific Derived0 arguments
return new(where) Derived0(args...);
}
The callers of these functions can either come from the codepath with
the "functor" argument, in which case they allocated properly aligned
storage on the stack and they will call the functor with argument on the
initialized object and then get rid of it or they come from the codepath
with the load returning auto_ptr<Base> in which case they will allocate
memory aligned for anything with :
perator new(sizeof(DerivedX)); and will
return an auto_ptr to it. And as such at some point auto_ptr will call
delete on the :
perator new returned memory which I need to know if it is
valid code.