Re: "Anonyomous" Objects
On Nov 10, 12:40 am, none <n...@none.none> wrote:
I am having great troubles with "anonymous" objects.
The correct name is "temporaries". Depending on the context,
the expression "rvalue" will also be used. (Technically,
objects are temporaries, and rvalue is a characteristic of an
expression. A temporary is created when an rvalue expression
results in an object.)
That might not be the correct term, but I just mean an
object created on the stack but never given a name.
These objects apparently do not follow the rules of
virtual inheritance.
The most certainly do. The only way they differ from other
objects is in their lifetime (to the end of the full
expression).
The code below outputs the following:
B::func()
B::func()
A::func()
A::func()
The behavior is the same on both g++ and in MS Visual
Studio 2005.
I get warnings when I compile with g++. In fact, the code is
illegal, and shouldn't compile.
The most obvious workaround would be to avoid using
anonymous objects. Unfortunately, I really NEED to use
them. I can explain the details of why I need to do
this, if it matters.
I've spent a day experimenting and I've found that the
behavior is very fickle. For example, removing the
constructor and destructor from class A changes the
output to:
B::func()
B::func()
B::func()
B::func()
Can anyone explain what's going on?
You have undefined behavior, so anything can happen.
----- begin code: -----
#include "stdio.h"
class A
{
public:
A() {}
virtual ~A() {}
virtual void func() { printf("A::func()\n"); }
};
class B: public A
{
public:
void func() { printf("B::func()\n"); }
};
class C: public B
{
};
C x()
{
C new_C;
return new_C;
}
Since you seem to be interested in temporary objects, you could
also just write:
C x()
{
return C();
}
The effect will be pretty much the same.
int main(int argc, char **argv)
{
// Call a method in an anonyomus object.
// This works.
x().func();
// Call a method in an anonymous object through a pointer.
// This looks a little strange, but works.
(&(x()))->func();
This shouldn't compile: "The result of the unary & operator is a
pointer to its operand. The operand shall be an lvalue or a
qualified-id"[=A75.3.1/2] and "A function call is an lvalue if and
only if the result type is a reference"[=A75.2.2/10]. I'm rather
surprised that any compiler accepts this, but both VC++ and g++
seem to (albeit with a warning in the case of g++); the
lvalue/rvalue distinction dates from the earliest days of C, and
you'd expect compilers to get anything this fundamental right.
// Should be exactly the same as above, but split into two lines.
// This calls A::func() even though func() is virtual!
A *A_ptr = &(x());
A_ptr->func();
Again, the first line shouldn't compile. In addition, the
lifetime of the temporary object in the first line is the end of
the full expression; after that, the object doesn't exist, and
any use of it is undefined behavior---anything can happen.
// This also calls A::func(), even though the pointer
// is of type "pointer-to-C"!!
C *C_ptr = &(x());
C_ptr->func();
Same problem as the above. You're using an object after it has
ceased to exist.
return 0;
}
----- end code -----
It might be interesting if you instrumented the classes, adding
explicit copy constructor, assignment, etc., and outputting what
is going on. You'll see that the destructor of the temporary
object is called at the end of the full expression in each case.
Technically, when the destructor of the derived class calls that
of the base class, the object takes on the type of the base
class; the compiler must ensure that during the destructor of
the base class, any virtual function calls (or use of typeid)
treats the object as being an object of the base class---in
practice, this means resetting the vptr. Depending on the code
generation strategy and the level of optimization, the compiler
may skip this step if it can determine that no virtual functions
are called in the destructor. This probably explains the
difference you see depending on whether you define a
(non-inlined) destructor for A, or the compiler provides its
default (inlined) destructor.
The compiler doesn't have to worry about anything which happens
to the object after the destructors have finished, since the
object may no longer be used.
--
James Kanze