Re: Constructing Derived in shell of Base <shudder>
On Jul 19, 6:46 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:
On Jul 18, 8:34 am, James Kanze <james.ka...@gmail.com> wrote:
On Jul 16, 7:26 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:
Well, let me phrase the question like this. Is the following code
legal?
#include <new>
class foo
{
public:
virtual void bar() {}
};
int main()
{
foo * f = new foo;
foo * f2 = new (f) foo;
f2 -> bar();
}
I would never write code like this as a matter of style and
sanity, but I would still like to know as a matter of
understanding all of the standard and its nuances. Does that
phrase disallow this?
No. Other things might; I'd have to check. Suppose, for
example, and implementation dyanamically allocated a new
copy of the vtable for each instance of the variable. (I
think this would be legal.) I'm not sure about what happens
when you reuse the memory underlying an object with
non-trivial destructors without calling the destructor.
C++03 standard, 3.8 Object Lifetime / 4
A program may end the lifetime of any object by reusing the
storage which the object occupies or by explicitly calling
the destructor for an object of a class type with
a non-trivial destructor. For an object of a class type with
a non-trivial destructor, the program is not required to call
the destructor explicitly before the storage which the object
occupies is reused or released; however, if there is no
explicit call to the destructor or if a delete-expression
(5.3.5) is not used to release the storage, the destructor
shall not be implicitly called and any program that depends
on the side effects produced by the destructor has undefined
behavior.
It seems like you can do such a thing. I would assume that
failing to call the destructor can result in a resource leak,
which is generally "bad", but which is not the standard's
"undefined behavior".
Failing to call the destructor will mean that whatever code
would have been executed in the destructor will not be executed.
Destructors are used for much more than just freeing resources.
As a separate question, I wonder why it's undefined behavior
to rely on the destructor's side effects in this case. I would
imagine the sensible thing would be "the destructor will not
be implicitly called". I would assume another open ended
allowance to the implementation with no specific
implementation technique in mind. Or perhaps, a debug
implementation could use it?
The compiler could very well insert some special code of its own
in the destructor, see below.
From James
Kanze's reply, that might not be the intent, nor how it's commonly
understood.
Either I misstated my understanding, or you misunderstood what I
said. The expression sizeof(Type) returns the number of bytes
necessary to "contain" an instance of the object. Whether all
of those bytes are part of the object or not is a separate
question. And I think (but I'm not sure) that the
implementation is allowed to allocate other memory in the
constructor, and free it in the destructor. (Consider the oft
sited alternative to a vptr---the compiler allocates an entry in
a map somewhere.)
Presumably we're not talking about duck typing, right? That is, the
compiler understood types are immutable, and no new types may be
defined. In such a case, a call to a virtual function would be a
string lookup in some sort of map container, but that map container
would be compile time constant. I don't see the need to modify this
map every time an object of the relevant type is created or destroyed.
Could you please explain further?
The classic example use a map void* -> vptr. The vptr didn't
reside in the object, but was maintained in a separate map,
indexed by the object address. If you don't call the
destructor, the entry in the map remains.
Given an answer to the above question is 1 or 3, I have an additional
question: What motivated this allowance?
The motivation for any allowances here is to allow a maximum of
freedom to the implementation.
So, no particular implementation technique in particular? Ok. Again,
mostly my curiosity.
I don't think that the committee really thought that the
technique described above would ever be used. But they did want
to keep all doors open.
In no case, however, is there
any intent of allowing certain common idioms to fail. (Or
mayby... Suppose you derive from an empty PODS. Can you still
memcpy to it?)
Interesting question. I don't think I would ever use memcpy on sub-
objects, at least not until I see some evidence that all relevant
compilers will not break when doing so.
It's *not* guaranteed. Think about it for a moment:
struct Base {};
struct Derived : Base { virtual ~Derived(); };
According to the standard, sizeof(Base) must be at least 1.
Empty base class optimization means that the vptr of Derived
could be at the same address as the Base subobject in Derived.
Any memcpy to the Base subobject will overwrite part of the
vptr. Which is likely to cause problems very, very quickly.
--
James Kanze