Virtual Methods Question

D

Daniel

I have the following three classes

class A
{
public:
virtual void f() = 0;
};

class B: public A
{
public:
virtual void f();
};

void B::f()
{
cout << "B::f()";
}

class C: public B
{
public:
void f();
};

void C::f()
{
cout << "C::f()";
}


main()
{
A* x = new C();

x->f();
}

The above program prints the following:

B::f()

But, I wanted the program to print

C::f()

Why is the program doing this? Is there any way for me to have C's f()
method invoked instead of B's f() method? I am using the GNU compiler
for VxWorks 5.5. Thanks in advance for any help.


-Daniel
 
R

roberts.noah

Daniel said:
The above program prints the following:

B::f()

But, I wanted the program to print

C::f()

prints "C::f()" over here. That is what it should do.
 
P

Peter_Julian

|
| |
| > class C: public B
| > {
| > public:
| > void f();
| > };
|
| you miss a virtual there.
|
| f~
|

Nope, if the pure-abstract or base class specifies that void f() is virtual
then its virtual in the entire hierarchy. Regardless whether the derived
classes have that function as virtual or not.
 
R

roberts.noah

Peter_Julian said:
|
| |
| > class C: public B
| > {
| > public:
| > void f();
| > };
|
| you miss a virtual there.
|
| f~
|

Nope, if the pure-abstract or base class specifies that void f() is virtual
then its virtual in the entire hierarchy. Regardless whether the derived
classes have that function as virtual or not.

I thought it stopped at C meaning that any subclass of C would not be
able to polymorphically override f().
 
P

Peter_Julian

| I have the following three classes
|
| class A
| {
| public:
| virtual void f() = 0;
| };

undefined behaviour.
1) you failed to invoke delete x
2) In main() you are using a base pointer to a derived object and yet you
have not declared a virtual destructor in class A.

#include <iostream>
#include <ostream>

class A
{
public:
virtual ~A() { std::cout << "~A()\n"; }
virtual void f() = 0;
};

<snip>

|
| main()

int main()

| {
| A* x = new C();

A* x = new C;

|
| x->f();

delete x;

| }
|
| The above program prints the following:
|
| B::f()
|
| But, I wanted the program to print
|
| C::f()
|
| Why is the program doing this? Is there any way for me to have C's f()
| method invoked instead of B's f() method? I am using the GNU compiler
| for VxWorks 5.5. Thanks in advance for any help.
|

Your compiler is invoking the C() ctor correctly(which requires that both
A() and B() get allocated first) and then drops the allocation of the
derived C as if it were a temporary (which is not appropriate but otherwise
not strictly enforced). The remnant is a valid B-type object.

That should be fixed with the correct statement:
A* x = new C; // correctly invokes the default ctor to completion

Note that your problem, other than not using delete x at all (shame), has a
much more critical issue than the one you raise. If you don't provide a
virtual d~tor in class A then ~B() or ~C() will never be invoked. Thats a
guarenteed memory leak.

Test it:

int main()
{
A* x = new C;

x->f();

delete x;

return 0;
}

/* correct output
C::f()
~C()
~B()
~A()
*/

but what you are getting with the compiler generated d~tor(s) is:

C::f()
~A()

which is a partial destruction and a memory leak. bad news.

Moral of the story:

a) never, ever new if you don't delete
b) never, ever new[] if you don't [] delete
c)if you derive and destroy via a pointer to base:
***always provide a virtual d~tor***
or suffer the consequences.
 
P

Peter_Julian

|
| Peter_Julian wrote:
| > | > |
| > | | > |
| > | > class C: public B
| > | > {
| > | > public:
| > | > void f();
| > | > };
| > |
| > | you miss a virtual there.
| > |
| > | f~
| > |
| >
| > Nope, if the pure-abstract or base class specifies that void f() is
virtual
| > then its virtual in the entire hierarchy. Regardless whether the derived
| > classes have that function as virtual or not.
|
| I thought it stopped at C meaning that any subclass of C would not be
| able to polymorphically override f().
|

That doesn't make sense, and with good reason. The is_a relationship is
critical here. If you publicly derive from C and declare D, then its implied
that the D-type is_a C-type as well. You can perfectly well derive like
so...

class D : public C
{
};

and never declare void f() since a call like so...

A* p_a = new D;
p_a->f(); // will call C::f(), no problem - its expected
delete p_a; // invokes 4 d~tors - ~D, ~C, ~B and ~A

If there was a way to prevent the inheritance of a pure-virtual member
function, polymorphism would be broken. And there is...
___
The answer to your quest is simple: composition or private inheritance.
Sometimes its more important to understand when not_to_use polymorphic
inheritance.
Neither of these represent an "is_a" inheritance scheme.

class D
{
C c;
public:
void f() { } // not virtual
};

or D in_terms_of C, where D is_not C or B or A. This is also a form of
composition, not polymorphic inheritance...

class D : private C
{
void f() { std::cout << "D::f()\n"; } // not virtual
};

int main()
{
D d;
d.f();
}

/*
D::f() // not virtual
~D()
~C()
~B()
~A()
*/
 
D

Dietmar Kuehl

Daniel said:
The above program prints the following:

B::f()

You must have made a copying error: the program does not compile
for me. There are missing include statements, namespace qualification,
and a missing return type of 'main()'. Once I fixed these problems,
the program prints "C::f()", as it should.
 
F

Frank Schmidt

Peter_Julian said:
|
| |
| > class C: public B
| > {
| > public:
| > void f();
| > };
|
| you miss a virtual there.
|
| f~
|

Nope, if the pure-abstract or base class specifies that void f() is
virtual
then its virtual in the entire hierarchy. Regardless whether the derived
classes have that function as virtual or not.

say that to a compiler, which calls B::f() with the given the source
 
R

roberts.noah

Peter_Julian said:
|
| Peter_Julian wrote:
| > | > |
| > | | > |
| > | > class C: public B
| > | > {
| > | > public:
| > | > void f();
| > | > };
| > |
| > | you miss a virtual there.
| > |
| > | f~
| > |
| >
| > Nope, if the pure-abstract or base class specifies that void f() is
virtual
| > then its virtual in the entire hierarchy. Regardless whether the derived
| > classes have that function as virtual or not.
|
| I thought it stopped at C meaning that any subclass of C would not be
| able to polymorphically override f().
|

That doesn't make sense, and with good reason. The is_a relationship is
critical here. If you publicly derive from C and declare D, then its implied
that the D-type is_a C-type as well. You can perfectly well derive like
so...

class D : public C
{
};

and never declare void f() since a call like so...

You totally missed my point. Doesn't matter because I was wrong but
still, I said one thing and you went off in a totally different
direction.
 
P

Peter_Julian

|
| | >
| > | > |
| > | | > |
| > | > class C: public B
| > | > {
| > | > public:
| > | > void f();
| > | > };
| > |
| > | you miss a virtual there.
| > |
| > | f~
| > |
| >
| > Nope, if the pure-abstract or base class specifies that void f() is
| > virtual
| > then its virtual in the entire hierarchy. Regardless whether the derived
| > classes have that function as virtual or not.
|
| say that to a compiler, which calls B::f() with the given the source
|

Wrong solution. The only reason that B::f() is being called is because the
call to new C() never return a type C instance [undefined behaviour]. The
remnant type at the pointer is a B-type object with some compilers.

The correct statement is: A* p = new C; // no brackets

insofar as the virtual keyword, test it yourself...:

___
#include <iostream>
#include <ostream>

class A
{
virtual void f();
};

class B : public A
{
void f();
};

class C : public B
{
void f();
};

int main()
{
C c;
A* p = &c;
p->f();

return 0;
}

/*
C::f()
*/
 
J

Jay_Nabonne

I have the following three classes

The above program prints the following:

B::f()

But, I wanted the program to print

C::f()

Why is the program doing this? Is there any way for me to have C's f()
method invoked instead of B's f() method?

The way the code is set up above, it should work. Make sure the function
signatures are *identical* including parameters, return type, and
"const"ness.

- jay
 
J

Jay_Nabonne

On Sun, 26 Feb 2006 23:50:54 -0500, Peter_Julian wrote:

I'll probably regret this...
| I have the following three classes
|
| class A
| {
| public:
| virtual void f() = 0;
| };

undefined behaviour.
1) you failed to invoke delete x

Failing to delete x is not undefined behavior. It's a memory leak.
2) In main() you are using a base pointer to a derived object and yet you
have not declared a virtual destructor in class A.

*Using* a derived object through a base pointer (without a virtual
destructor) is not undefined behavior (see, for example, COM). *Deleting*
said pointer would be. Since the pointer was not deleted, there is no
undefined behavior. (But as soon as you add your delete, then you need to
add the virtual destructor.) I'm not saying it's good code (and you
provide an excellent analysis below), but there's no undefined behavior as
is.
| {
| A* x = new C();

A* x = new C;

There is no difference in the above two lines for the C class, at least as
far as the stability of the resulting object goes.
Your compiler is invoking the C() ctor correctly(which requires that both
A() and B() get allocated first) and then drops the allocation of the
derived C as if it were a temporary (which is not appropriate but otherwise
not strictly enforced). The remnant is a valid B-type object.

Have you been smoking the C++ standard again? ;) Seriously, what are you
talking about? What in the world would cause the newly allocated "C"
object to decay to a "B"?

- Jay
 
P

Peter_Julian

| On Sun, 26 Feb 2006 17:34:58 -0800, Daniel wrote:
|
| > I have the following three classes
|
| <snip>
|
| >
| > The above program prints the following:
| >
| > B::f()
| >
| > But, I wanted the program to print
| >
| > C::f()
| >
| > Why is the program doing this? Is there any way for me to have C's f()
| > method invoked instead of B's f() method?
|
| The way the code is set up above, it should work. Make sure the function
| signatures are *identical* including parameters, return type, and
| "const"ness.
|

The return type is not part of the signature and is not taken in
consideration when deciding which function gets called.
 
P

Peter_Julian

| On Sun, 26 Feb 2006 23:50:54 -0500, Peter_Julian wrote:
|
| I'll probably regret this...

lol, no we won't, i welcome the feedback specially if it proves me wrong

|
| >
| > | > | I have the following three classes
| > |
| > | class A
| > | {
| > | public:
| > | virtual void f() = 0;
| > | };
| >
| > undefined behaviour.
| > 1) you failed to invoke delete x
|
| Failing to delete x is not undefined behavior. It's a memory leak.

The mem leak wasn't what made the code UB.

|
| > 2) In main() you are using a base pointer to a derived object and yet
you
| > have not declared a virtual destructor in class A.
|
| *Using* a derived object through a base pointer (without a virtual
| destructor) is not undefined behavior (see, for example, COM). *Deleting*
| said pointer would be. Since the pointer was not deleted, there is no
| undefined behavior. (But as soon as you add your delete, then you need to
| add the virtual destructor.) I'm not saying it's good code (and you
| provide an excellent analysis below), but there's no undefined behavior as
| is.

Wrong, new used to allocate that base pointer, whether its explicitly
deleted or not, is UB. Consider: shared_ptr<T>, scoped_ptr<T>, etc.

|
| >
| >
| > | {
| > | A* x = new C();
| >
| > A* x = new C;
| >
|
| There is no difference in the above two lines for the C class, at least as
| far as the stability of the resulting object goes.

This, unfortunately, has been proven to be false. The OP's result is a case
in point.

|
| >
| > Your compiler is invoking the C() ctor correctly(which requires that
both
| > A() and B() get allocated first) and then drops the allocation of the
| > derived C as if it were a temporary (which is not appropriate but
otherwise
| > not strictly enforced). The remnant is a valid B-type object.
|
| Have you been smoking the C++ standard again? ;) Seriously, what are you
| talking about? What in the world would cause the newly allocated "C"
| object to decay to a "B"?
|

Beats the hell out of me (placement new implementation?). Won't be the first
time i've seen that. It is, however, correct to write ...

int* p = new int;

.... to invoke the default ctor. Even the problem compilers handle that
correctly. I'ld be interested to know what that GNU compiler for VxWorks 5.5
does without the brackets (hint?).

[Hmm, now where the hell is that pint of beer()?]
 
J

Jay_Nabonne

| On Sun, 26 Feb 2006 17:34:58 -0800, Daniel wrote:
|
| > I have the following three classes
|
| <snip>
|
| >
| > The above program prints the following:
| >
| > B::f()
| >
| > But, I wanted the program to print
| >
| > C::f()
| >
| > Why is the program doing this? Is there any way for me to have C's f()
| > method invoked instead of B's f() method?
|
| The way the code is set up above, it should work. Make sure the function
| signatures are *identical* including parameters, return type, and
| "const"ness.
|

The return type is not part of the signature and is not taken in
consideration when deciding which function gets called.

In general, you are right, but I'm not sure how that relates to this
problem.

Since the function call is through the A*, there is only one
function signature to examine (the one in A). As it turns out, if the
virtual function in C had a different return type, it wouldn't compile
anyway, so my suggestion to check the return type doesn't help here. But
the rest are valid. For example, this code prints "B" because the function
"f" in class C doesn't match the signature of the base class:

class A
{
public:
virtual void f() = 0;
};

class B : public A
{
virtual void f()
{
printf("B\n");
}
};

class C : public B
{
void f() const
{
printf("C\n");
}
};

int main()
{
A* p = new C;
p->f();
return 0;
}

- Jay
 
J

Jay_Nabonne

| On Sun, 26 Feb 2006 23:50:54 -0500, Peter_Julian wrote:
|
| I'll probably regret this...

lol, no we won't, i welcome the feedback specially if it proves me wrong

|
| >
| > | > | I have the following three classes
| > |
| > | class A
| > | {
| > | public:
| > | virtual void f() = 0;
| > | };
| >
| > undefined behaviour.
| > 1) you failed to invoke delete x
|
| Failing to delete x is not undefined behavior. It's a memory leak.

The mem leak wasn't what made the code UB.

|
| > 2) In main() you are using a base pointer to a derived object and yet
you
| > have not declared a virtual destructor in class A.
|
| *Using* a derived object through a base pointer (without a virtual
| destructor) is not undefined behavior (see, for example, COM). *Deleting*
| said pointer would be. Since the pointer was not deleted, there is no
| undefined behavior. (But as soon as you add your delete, then you need to
| add the virtual destructor.) I'm not saying it's good code (and you
| provide an excellent analysis below), but there's no undefined behavior as
| is.

Wrong, new used to allocate that base pointer, whether its explicitly
deleted or not, is UB. Consider: shared_ptr<T>, scoped_ptr<T>, etc.

Can you provide a reference for that statement? Since no shared_ptr or
scoped_ptr are used here, they're irrelevant. How is allocating the
derived object and not freeing it UB?
| > | {
| > | A* x = new C();
| >
| > A* x = new C;
| >
| >
| There is no difference in the above two lines for the C class, at
| least as far as the stability of the resulting object goes.

This, unfortunately, has been proven to be false. The OP's result is a
case in point.

The following code works fine for me (prints "C"):

class A
{
public:
virtual void f() = 0;
};

class B : public A
{
virtual void f()
{
printf("B\n");
}
};

class C : public B
{
void f() const
{
printf("C\n");
}
};

int main()
{
A* p = new C();
p->f();
return 0;
}

which implies either the OP was using different code or the compiler is
broken.
| > Your compiler is invoking the C() ctor correctly(which requires that
both
| > A() and B() get allocated first) and then drops the allocation of
| > the derived C as if it were a temporary (which is not appropriate
| > but
otherwise
| > not strictly enforced). The remnant is a valid B-type object.
|
| Have you been smoking the C++ standard again? ;) Seriously, what are
| you talking about? What in the world would cause the newly allocated
| "C" object to decay to a "B"?
|
|
Beats the hell out of me (placement new implementation?). Won't be the
first time i've seen that. It is, however, correct to write ...

int* p = new int;

... to invoke the default ctor. Even the problem compilers handle that
correctly. I'ld be interested to know what that GNU compiler for VxWorks
5.5 does without the brackets (hint?).

If you're suggesting it's a compiler error, then I can get in line with
it. I thought you were suggesting that:

A* p = new C();

could (with a correct implementation) be different than:

A* p = new C;

which is not a true statement, to my knowledge and experience.

- Jay
 
P

Peter_Julian

| On Tue, 28 Feb 2006 19:56:44 -0500, Peter_Julian wrote:
|
| >
| > | > | On Sun, 26 Feb 2006 17:34:58 -0800, Daniel wrote:
| > |

<snip>

|
| Since the function call is through the A*, there is only one
| function signature to examine (the one in A). As it turns out, if the
| virtual function in C had a different return type, it wouldn't compile
| anyway, so my suggestion to check the return type doesn't help here. But
| the rest are valid. For example, this code prints "B" because the function
| "f" in class C doesn't match the signature of the base class:
|
| class A
| {
| public:
| virtual void f() = 0;
| };
|
| class B : public A
| {
| virtual void f()
| {
| printf("B\n");
| }
| };
|
| class C : public B
| {
| void f() const
| {
| printf("C\n");
| }
| };
|
| int main()
| {
| A* p = new C; // undefined behaviour
| p->f();
| return 0;
| }
|
| - Jay

Non-const member functions have a default this parameter. Adding the const
modifier simply strips away the this parameter. So f() in C does not have
the same signature. Its got nothing to do with the return type.

Every cycle through the above program will leak memory since delete p was
never called to invoke C's d~tor (of course, debug mode may help depending
on your debugger). If you still don't think thats undefined behaviour: write
a function that leaks a new allocation and loop through it a few thousand
times in release mode.

Anything could happen, it may format your hard drive, it might crash your
OS, it may do nothing else except lower your resources. That, in effect, is
the definition of undefined behaviour.
 
J

Jay_Nabonne

On Sun, 05 Mar 2006 02:11:19 -0500, Peter_Julian wrote:

<snip>

| A* p = new C; // undefined behaviour

Please explain. I don't think I'm asking for much. Just justification for
your statements. :)
Non-const member functions have a default this parameter. Adding the const
modifier simply strips away the this parameter. So f() in C does not have
the same signature. Its got nothing to do with the return type.

I don't recall saying it did. I was pointing out (hopefully for the OP's
benefit) that having difference of const can cause the behavior he was
seeing. (I once helped someone solve exactly that problem, which is why
I'm pointing it out.)
Every cycle through the above program will leak memory since delete p was
never called to invoke C's d~tor (of course, debug mode may help depending
on your debugger). If you still don't think thats undefined behaviour: write
a function that leaks a new allocation and loop through it a few thousand
times in release mode.

I don't think it's "undefined behavior" in the strict C++ sense, no. If
there is a place in the C++ specification that contradicts that, then I'm
more than willing to change my thoughts.
Anything could happen, it may format your hard drive, it might crash your
OS, it may do nothing else except lower your resources. That, in effect, is
the definition of undefined behaviour.

There's "undefined behavior" in the English sense, and there's "undefined
behavior" in the C++ sense. Cases of the latter are explictly called out
in the specification. Again, please state where a memory leak is called
out as C++ undefined behavior in the specification.

I've said all I plan to on this (short of retracting my statements if
disproved by the specification). You seem to be dodging my requests for
you to validate your statements, and I don't see any gain to asking you
any further.

- Jay
 
J

Jay_Nabonne

On Sun, 05 Mar 2006 02:11:19 -0500, Peter_Julian wrote:

One more thought...
Every cycle through the above program will leak memory since delete p was
never called to invoke C's d~tor (of course, debug mode may help depending
on your debugger). If you still don't think thats undefined behaviour: write
a function that leaks a new allocation and loop through it a few thousand
times in release mode.

Anything could happen, it may format your hard drive, it might crash your
OS, it may do nothing else except lower your resources. That, in effect, is
the definition of undefined behaviour.

Consider the following code:

#include <list>

typedef std::list<unsigned char*> MyList;

int main()
{
MyList l;

// Allocate the world.
try
{
for (;;)
{
l.push_back(new unsigned char[1024]);
}
}
catch(...)
{
}

// Release the world.
for (MyList::iterator i = l.begin(); i != l.end(); ++i)
{
delete [] *l;
}
}

This code will (in theory) use up a tremendous amount of memory, only
stopping when it is no longer able to allocate memory. Following the logic
you expressed above, since it's unclear what the OS will do when it gets
low on memory, then the above code exhibits "undefined behavior" (I'm
setting up the same conditions, more or less, that the offending
non-delete code had). It could, according to you, format the hard drive.

And yet (assuming I coded it right off the top of my head) all new's are
paired with delete's. So how to identify the "undefined behavior"?

Again, even though the effect of the above code on an OS is not
necessarily predictable, it is not "undefined behavior" as defined by the
C++ standard. So the effect of a memory leak on the OS is immaterial since
the above code, which has no undefined behavior as defined by the C++ spec
duplicates the conditions you described.

- Jay
 

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
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top