Why '(&b) -> f() ' is static binding?

Z

zhangyafei_kimi

I am puzzled by a face test, which source code is like below:

#include <iostream>

using namespace std ;



class base

{

public:

virtual void f() { cout << "base::f()" << endl ; }

};



class derive : public base

{

public:

void f() { cout << "derive::f()" << endl ; }

};



int main()

{

base b ;

(&b) -> f() ;//why this f() is static bound?

(&b) -> ~base() ;

new (&b) derive ;

(&b) -> f() ;//why this f() is static bound?

base *p = &b ;

p->f() ;//it is easy to understand this line



system("pause");

return 0 ;

}



The result of this short program is so peculiar and like this:

base::f()

base::f()

derive::f()



In my opinion, the result of expression '(&b)' is a 'base*' type, so
it will cause a polymorphic behavior. But why '(&b) -> f();' is static
bound?



After referring some authoritative documents, I still can not find the
reason. So I am doubting that the standards did not clearly state this
situation. I am eager to know the reason and the fact.
 
O

Owen Jacobson

base b ;
....

(&b) -> ~base() ;

Undefined behaviour. The variable 'b' was not creating using
placement new, and the object is not being destoryed; you are not
permitted to invoke the destructor here.
new (&b) derive ;

Undefined behaviour. The variable 'b' must hold an instance of base,
not any other class. The compiler is permitted to assume this and
skip virtual dispatch for methods on b, which is what you're
experiencing; however, the compiler is also permitted to format your
hard drive, bill eighteen hours of tranny porn to your credit card,
and invite your in-laws to live with you on encountering code like
this. *Anything* can happen, since C++ makes no guarantees about
undefined behaviour.
 
M

Markus Moll

Hi

Owen said:
Undefined behaviour. The variable 'b' was not creating using
placement new, and the object is not being destoryed; you are not
permitted to invoke the destructor here.

Um... what was that famous saying again? "Chapter and verse, please?"
I don't see anything that would forbid explicitly calling the destructor for
objects with automatic storage duration. On the contrary, the example in
12.4(14) seems to suggest that it's perfectly fine.
Undefined behaviour.

Agreed, but only because I think there are no guarantees about alignment.
The variable 'b' must hold an instance of base, not any other class.

Phew... only partially agreed. I think there may be other objects residing
in the memory of b, given that size and alignment requirements are
satisfied. However...
The compiler is permitted to assume this and
skip virtual dispatch for methods on b,

.... you may not access any other (non-"base") object through b (3.10(15)).
This also says that the implicit destruction of b will result in undefined
behavior (because the destructor for base is called).

I think the following would be fine, though:

#include <new>

struct X { int a; int b; };

int main()
{
X x;
x.~X();
new(&x) int; // x must be properly aligned for ints (9.2(17))
int& y = reinterpret_cast<int&>(x);
y = 5;
y.~int();
new(&x) X;
}

Markus
 
A

Andre Kostur

(e-mail address removed) wrote in @e34g2000pro.googlegroups.com:
I am puzzled by a face test, which source code is like below:

#include <iostream>

using namespace std ;



class base

{

public:

virtual void f() { cout << "base::f()" << endl ; }

};



class derive : public base

{

public:

void f() { cout << "derive::f()" << endl ; }

};



int main()

{

base b ;

(&b) -> f() ;//why this f() is static bound?

The compiler knows that b is a base and probably optimize away doing the
(&b)->f() into b.f();
(&b) -> ~base() ;

new (&b) derive ;

Somehow this seems like undefined behaviour... I just can't quote why
(unless you add a member to derive, than I know it's UB).
(&b) -> f() ;//why this f() is static bound?

b is _still_ a base object, known at compile time.

base *p = &b ;

p->f() ;//it is easy to understand this line

In this case, the compiler only knows that p points to a base object, or
some derived class from base, so it must consult whatever virtual
mechanism the compiler uses to find the actual function to invoke.
 
G

Guest

Undefined behaviour. The variable 'b' was not creating using
placement new, and the object is not being destoryed; you are not
permitted to invoke the destructor here.

Having seen a similar question not long ago I became interested in what
the standard says about this, but from what I can tell there is nothing
preventing you from calling the destructor of an object with automatic
storage duration. You will however get undefined behaviour when the
object would normally be destructed since an object does no longer exist
after its destructor has been called (12.4 §14).

If the standard says different somewhere else I would be happy to learn
where.

3.7.2 §3 might also be relevant: "If a named automatic object has
initialization or a destructor with side effects, it shall not be
destroyed before the end of its block, ..."

Of course, trying to access an object in any way (including taking its
address) after it has been destroyed results in undefined behaviour.
 
G

Guest

Hi



Um... what was that famous saying again? "Chapter and verse, please?"
I don't see anything that would forbid explicitly calling the destructor for
objects with automatic storage duration. On the contrary, the example in
12.4(14) seems to suggest that it's perfectly fine.


Agreed, but only because I think there are no guarantees about alignment.


Phew... only partially agreed. I think there may be other objects residing
in the memory of b, given that size and alignment requirements are
satisfied. However...


... you may not access any other (non-"base") object through b (3.10(15)).
This also says that the implicit destruction of b will result in undefined
behavior (because the destructor for base is called).

I think the following would be fine, though:

There are two things that makes me unsure.
#include <new>

struct X { int a; int b; };

int main()
{
X x;
x.~X();
new(&x) int; // x must be properly aligned for ints (9.2(17))

After the destructor has been invoked the object no longer exists, can
you then take the address of it?
int& y = reinterpret_cast<int&>(x);
y = 5;
y.~int();
new(&x) X;
}


And here x's destructor would be implicitly invoked, which would result
in UB.

This makes me wonder, is it possible to explicitly invoke the destructor
of an automatic object and not end up with UB? It would require a way to
exit a block in a way that would normally not implicitly invoke the
objects destructor.
 
M

Markus Moll

Hi
There are two things that makes me unsure.


After the destructor has been invoked the object no longer exists, can
you then take the address of it?

Sure.

3.8(6) [Object Lifetime]
Similarly, before the lifetime of an object has started but after the
storage which the object will occupy has been allocated or, after the
lifetime of an object has ended and before the storage which the object
occupied is reused or released, any lvalue which refers to the original
object may be used but only in limited ways. Such an lvalue refers to
allocated storage (3.7.3.2), and using the properties of the lvalue which
do not depend on its value is well-defined.
And here x's destructor would be implicitly invoked, which would result
in UB.

That was the reason why, at the very end, I again constructed an X
object ;-)
This makes me wonder, is it possible to explicitly invoke the destructor
of an automatic object and not end up with UB? It would require a way to
exit a block in a way that would normally not implicitly invoke the
objects destructor.

No, IMO it just requires that at the end, before the scope is left, there is
an object of the original type.

Markus
 
M

Markus Moll

Hi

Erik Wikström wrote:
3.7.2 §3 might also be relevant: "If a named automatic object has
initialization or a destructor with side effects, it shall not be
destroyed before the end of its block, ..."

This is in my opinion just a very bad formulation. It is meant to describe
that the implementation cannot destroy an object earlier than at the end of
the scope if the effect would be visible. I don't think it is meant to
limit explicit destruction, although at the moment it is admittedly unclear
if it does.

Markus
 
J

James Kanze

This is in my opinion just a very bad formulation. It is meant
to describe that the implementation cannot destroy an object
earlier than at the end of the scope if the effect would be
visible. I don't think it is meant to limit explicit
destruction, although at the moment it is admittedly unclear
if it does.

It's hard to say, but from the context, I think the above refers
to what an implementation is allowed to do: an implementation
cannot call the destructor of an object with a non trivial
destructor before the object goes out of scope, simply because
the object appears unused. I don't think it really necessary to
say this---the standard describes very clearly when the
abstract machine should call the destructor, and an
implementation can only differ from the abstract machine when it
can prove that the difference leads to no change in observable
behavior. But I can imagine that in the 1990's, when this text
was being formulated, that this wasn't so "obvious"; I know that
some early compilers I used complained that the variable wasn't
used when I used RAII or similar patterns, and of course in C
(where there are no destructors), the compiler is free to
eliminate the variable as soon as it is no longer used.

I'm very sure that something like:

{
T var ;
// ...
var.~T() ;
new (&var) T ;
// ...
}

is legal and well defined (unless the constructor of T happens
to raise an exception). I'm less sure about changing the type.
On the other hand, the compiler is allowed to assume that the
expression 'var' doesn't change type. Any use of var to refer
to anything other than a T (or an array of char or unsigned
char---you can look at raw memory) is undefined behavior.
 
G

Guest

It's hard to say, but from the context, I think the above refers
to what an implementation is allowed to do: an implementation
cannot call the destructor of an object with a non trivial
destructor before the object goes out of scope, simply because
the object appears unused. I don't think it really necessary to
say this---the standard describes very clearly when the
abstract machine should call the destructor, and an
implementation can only differ from the abstract machine when it
can prove that the difference leads to no change in observable
behavior. But I can imagine that in the 1990's, when this text
was being formulated, that this wasn't so "obvious"; I know that
some early compilers I used complained that the variable wasn't
used when I used RAII or similar patterns, and of course in C
(where there are no destructors), the compiler is free to
eliminate the variable as soon as it is no longer used.

I'm very sure that something like:

{
T var ;
// ...
var.~T() ;
new (&var) T ;
// ...
}

is legal and well defined (unless the constructor of T happens
to raise an exception). I'm less sure about changing the type.

3.8 §7:

"If, after the lifetime of an object has ended and before the storage
which the object occupied is reused or released, a new object is created
at the storage location which the original object, a pointerthat pointed
to the original object, a reference that referred to the original
object, or a name of the original object will automaticalle refer to the
new object and, once the lifetime of the new object has started, can be
used to manipulate the new object, if:

* the storage of the new object exactly overlays the storage location
which the original object occupied, and

* the new object is of the same type as the original object (ignoring
the top-level cv-qualifiers), and

* the original object was a most derived object (1.8)of type T and the
new object is a most derived object of type T (that is, they are not
base class subobjects)."

To me it seems like all but the last condition (perhaps with an addition
for the cv-qualifiers) are superfluous, since most derived objects of
the same type should have the same size and are of the same size, but
there might be some subtle points I am missing.
 
R

Ron Natalie

Markus said:
Hi



Um... what was that famous saying again? "Chapter and verse, please?"
I don't see anything that would forbid explicitly calling the destructor for
objects with automatic storage duration. On the contrary, the example in
12.4(14) seems to suggest that it's perfectly fine.

It's fine provided you assure that you don't destruct the object again.
The placement new that follows would provide for that.
Agreed, but only because I think there are no guarantees about alignment.

or size.
 
M

Markus Moll

Hi
3.8 §7:

Thanks... in fact, 3.8(8) gives the answer:

If a program ends the lifetime of an object of type T with static (3.7.1) or
automatic (3.7.2) storage duration and if T has a non-trivial destructor,
the program must ensure that an object of the original type occupies that
same storage location when the implicit destructor call takes place;
otherwise the behavior of the program is undefined. This is true even if
the block is exited with an exception.

This pretty much sums it up.

Markus
 
M

Markus Moll

Hi

Markus said:
Thanks... in fact, 3.8(8) gives the answer:

If a program ends the lifetime of an object of type T with static (3.7.1)
or automatic (3.7.2) storage duration and if T has a non-trivial
destructor, the program must ensure that an object of the original type
occupies that same storage location when the implicit destructor call
takes place; otherwise the behavior of the program is undefined. This is
true even if the block is exited with an exception.

This pretty much sums it up.

Um... sorry... forgot to post the important part, which would be the example
below that paragraph:

class T { };
struct B {
ËœB();
};
void h() {
B b;
new (&b) T;
} // undefined behavior at block exit

It pretty much makes it clear that (temporarily) using the space for objects
of different types is well-defined, only when the destructor is called,
types must match again. (I know how examples are non-normative...)

Markus
 
J

James Kanze

On 2007-10-23 11:25, James Kanze wrote:

[...]
"If, after the lifetime of an object has ended and before the storage
which the object occupied is reused or released, a new object is created
at the storage location which the original object, a pointerthat pointed
to the original object, a reference that referred to the original
object, or a name of the original object will automaticalle refer to the
new object and, once the lifetime of the new object has started, can be
used to manipulate the new object, if:
* the storage of the new object exactly overlays the storage location
which the original object occupied, and
* the new object is of the same type as the original object (ignoring
the top-level cv-qualifiers), and
* the original object was a most derived object (1.8)of type T and the
new object is a most derived object of type T (that is, they are not
base class subobjects)."
To me it seems like all but the last condition (perhaps with an addition
for the cv-qualifiers) are superfluous, since most derived objects of
the same type should have the same size and are of the same size, but
there might be some subtle points I am missing.

I think they're also trying to disallow things like:

p->~B() ;
new ( p + i ) B ;

where the new object is created in the same storage, but with a
different start address. I think that the goal is (was) to
allow something like my example, but nothing else.

When all is said and done, given the possibility of exceptions
from a constructor, I'd say that there would really be no harm
in disallowing even my example. The simple answer is "don't do
it". Regardless of what the standard says.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,197
Messages
2,571,041
Members
47,643
Latest member
ashutoshjha_1101

Latest Threads

Top