questions about dynamic binding

J

Jess

Hello,

I have some questions to do with dynamic binding. The example program
is:

#include<iostream>

using namespace std;

class A{
public:
virtual void f(){cout << "A::f()" << endl;}
};

class B:public A{
public:
void f(){cout << "B::f()" << endl;}
};

int main(){
B b;
A* bp = &b;
// A* bp2 = new B(*bp);
A* bp2 = new A(*bp);
bp2->f();
bp->f();
return 0;
}

As I expected "bp->f()" calls B's f(). However, "bp2->f()" calls A's
f(). Is it because of the "new A", which only copies the A's part of
"*bp" object, or is it because of the behaviour of the synthesized
copy constructor of A? If it is the latter, then can I create a B
object by defining my own copy constructor for A?

In addition, the statement that is commented out produced compiler
error. It says "no matching function for call to 'B::B(A&)'". Can't
compiler find out *bp is in fact a B object?

Thanks,
Jess
 
P

Paolo Maldini

// bp points to the object b, even if it is a pointer of class A.
A* bp = &b;

// so the member function B::f should be invoked.
bp->f();

// bp2 points to a object of class A.
A* bp2 = new A(*bp);

// so the member function A::f should be invoked.
bp2->f();

about the statement A* bp2 = new A(*bp);
if class A hasn't any copy constructor, the compiler will use the default
copy constructor to construct a new object.
A::A( const A& a),
because class B derives from class A, so the compiler will use A's part of
the object B to construct the object of class A.

if you write a overload copy constructor for class A, such as
A::A( const B& b),
the compiler will use your custom copy constructor to create the object of
class A.
yes, the error is reported at compile time, the dynamic binding is at run
time. so the compiler could not know whether a pointer points to its actual
object at compile time.
 
J

Jess

about the statement A* bp2 = new A(*bp);
if class A hasn't any copy constructor, the compiler will use the default
copy constructor to construct a new object.
A::A( const A& a),
because class B derives from class A, so the compiler will use A's part of
the object B to construct the object of class A.

So, this is the default behaviour of compiler?
if you write a overload copy constructor for class A, such as
A::A( const B& b),
the compiler will use your custom copy constructor to create the object of
class A.

Is there a way that I can overload the copy constructor so that the
object created is in fact of class B?

Thanks,
Jess
 
?

=?ISO-8859-1?Q?Erik_Wikstr=F6m?=

Hello,

I have some questions to do with dynamic binding. The example program
is:

#include<iostream>

using namespace std;

class A{
public:
virtual void f(){cout << "A::f()" << endl;}
};

class B:public A{
public:
void f(){cout << "B::f()" << endl;}
};

int main(){
B b;
A* bp = &b;
// A* bp2 = new B(*bp);
A* bp2 = new A(*bp);
bp2->f();
bp->f();
return 0;
}

As I expected "bp->f()" calls B's f(). However, "bp2->f()" calls A's
f(). Is it because of the "new A", which only copies the A's part of
"*bp" object, or is it because of the behaviour of the synthesized
copy constructor of A?

It's because new A() will create a new object of type A, so when doing
bp2->f() it will call the f() method of the object pointed to by bp2
(which happens to be of type A). When you do bp->f() the type of the
object pointed to is of type B so its f() is called.
If it is the latter, then can I create a B object by defining my own
copy constructor for A?

No, any constructor in an object (regardless if it's a normal
constructor or a copy-constructor) of type T will create an object of
type T, so when you do 'new A()' the type of the object will always be A
(since that is what you specified).
In addition, the statement that is commented out produced compiler
error. It says "no matching function for call to 'B::B(A&)'". Can't
compiler find out *bp is in fact a B object?

No, bp is a pointer to an object of type A, you can either cast the
pointer to a pointer of type B or create a constructor in B which takes
an A as argument.
 
J

James Kanze

I have some questions to do with dynamic binding. The example program
is:

using namespace std;
class A{
public:
virtual void f(){cout << "A::f()" << endl;}

};
class B:public A{
public:
void f(){cout << "B::f()" << endl;}

};
int main(){
B b;
A* bp = &b;
// A* bp2 = new B(*bp);
A* bp2 = new A(*bp);
bp2->f();
bp->f();
return 0;
}
As I expected "bp->f()" calls B's f(). However, "bp2->f()" calls A's
f().

That's because, as written, bp2 points to an A.
Is it because of the "new A", which only copies the A's part of
"*bp" object, or is it because of the behaviour of the synthesized
copy constructor of A? If it is the latter, then can I create a B
object by defining my own copy constructor for A?

It's because in the expression "new A", you tell the compiler
explicitly to create an A object. All of the rest follows;
there is no way that the expression "new A" can create anything
other than an A.

It's a classical case of the compiler doing what you told it
to, and not what you wanted. Otherwise known as "you asked for
it, you got it."
In addition, the statement that is commented out produced compiler
error. It says "no matching function for call to 'B::B(A&)'". Can't
compiler find out *bp is in fact a B object?

No, because it might not be. Virtuality only works on the
object the function is called on. Since no object exists when
the constructor is called, it cannot be virtual, and of course,
virtuality never works on arguments.

The classical solution here is to provide a clone function in
the base class, i.e.:

class A
{
public:
A* clone() const
{
A* result = doClone() ;
assert( typeid( *result ) == typeid( *this ) ) ;
return result ;
}

private:
virtual A* doClone() const
// Except that usually, this will be pure virtual.
// It's fairly rare to have virtual functions in a
// base class which aren't pure virtual.
{
return new A( *this ) ;
}
} ;

class B
{
private:
virtual A* doClone() const
{
return new B( *this ) ;
}
} ;

When you want to copy, you use clone, rather than new. (I've
also occasionally found it useful to have a function "another",
along the same lines as clone(), but using default construction
rather than copy.)
 
G

Gianni Mariani

The classical solution here is to provide a clone function in
the base class, i.e.:

class A
{
public:
A* clone() const
{
A* result = doClone() ;
assert( typeid( *result ) == typeid( *this ) ) ;
return result ;
}

private:
virtual A* doClone() const
// Except that usually, this will be pure virtual.
// It's fairly rare to have virtual functions in a
// base class which aren't pure virtual.
{
return new A( *this ) ;
}
} ;

class B
{
private:
virtual A* doClone() const
{
return new B( *this ) ;
}

If it was not a private method, it may be useful to define clone
methods to return the actual type they create. The compiler handles
upcasting for you if it is called from a base class. This is called a
co-variant return.

virtual B* doClone() const
{
return new B( *this ) ;
}
 
J

James Kanze

If it was not a private method, it may be useful to define clone
methods to return the actual type they create. The compiler handles
upcasting for you if it is called from a base class. This is called a
co-variant return.
virtual B* doClone() const
{
return new B( *this ) ;
}

I know that the possibility exists, but I have my doubts with
regards to the utility. If for some reason, the interface
specification of B includes this extension, it is relatively
easy to add another public function along the lines of that in
A. With, of course, the same post-condition.

(In practice, I'm not sure just how important the verification
of the post condition is in this case. In the vast majority of
the cases, the virtual clone function can---and in fact
must---be pure in the base class, and there is only one level of
inheritance, so the compiler will not allow you to instantiate a
class which forgets it. And even in the other cases, it's the
sort of thing that tends to be spotted immediately in code
review. But the technique of using a non-virtual public
function to verify pre- and post-conditions and invariants is
generally useful, and worth knowing and practicing.)
 
J

Jess

class A
{
public:
A* clone() const
{
A* result = doClone() ;
assert( typeid( *result ) == typeid( *this ) ) ;
return result ;
}

Why do you need to check typeid? "clone" isn't virtual, then will the
correct version be called if I try to "clone" a B object? Is it true
that if a non-virtual function (here "clone") calls a virtual function
(doClone),then the caller function is automatically virtual?
private:
virtual A* doClone() const
// Except that usually, this will be pure virtual.
// It's fairly rare to have virtual functions in a
// base class which aren't pure virtual.
{
return new A( *this ) ;
}
} ;

Why is it necessary to have another virtual function here that is also
private? What if I put this line of code into "clone()"?
class B
{
private:
virtual A* doClone() const
{
return new B( *this ) ;
}
} ;

If I have a B object "b", then if I do "b.clone()", then "A"'s clone()
method will be called, is it right? I guess what happens next is that
A's clone() calls "doClone()", since this is virtual, then B's
doClone" is called.

I'm still not sure why doClone is needed. Would it work if I do:

class A
{
public:
virtual A* clone() const
{
A* result = new A(*this) ;
assert( typeid( *result ) == typeid( *this ) ) ;
return result ;
}
};

class B
{
B* clone() const
{
return new B( *this ) ;
}
} ;

Thanks,
Jess
 
J

Jess

If it was not a private method, it may be useful to define clone
methods to return the actual type they create. The compiler handles
upcasting for you if it is called from a base class. This is called a
co-variant return.

virtual B* doClone() const
{
return new B( *this ) ;
}

Thanks for pointing out the co-variant return type. Does it make no
difference if the return is A* or B*?

Jess
 
J

James Kanze

Why do you need to check typeid?

To catch post-condition violations.
"clone" isn't virtual, then will the
correct version be called if I try to "clone" a B object?

"clone" isn't virtual, because the only correct version is in
the base class. "clone" calls a virtual function, "doClone" to
do the actual work.

This is the usual C++ implementation of programming by contract.
Is it true that if a non-virtual function (here "clone") calls
a virtual function (doClone),then the caller function is
automatically virtual?

No. The whole point of the exercise is that it is impossible to
call the virtual function without the post-condition checks.
Why is it necessary to have another virtual function here that is also
private? What if I put this line of code into "clone()"?

It won't work, because clone() isn't virtual.
If I have a B object "b", then if I do "b.clone()", then "A"'s clone()
method will be called, is it right? I guess what happens next is that
A's clone() calls "doClone()", since this is virtual, then B's
doClone" is called.

Right. A defines a contract. In this case, we've done nothing
in B to define a new contract, so A's contract still holds.
Which means that we start by calling a function in A, in order
to enforce the contract.

In practice, this is the usual situation. I'd guess that in
something like 90% of the cases involving inheritance, you
define a contract in a base class, and inherit, one level deep,
to provide different implementations. Practically, the only
time the user sees the derived class is when he constructs one;
all other accesses are through the base class.

There are cases, however, where the derived class wants to
provide an extended contract. Thus, for example, B might decide
to offer an extended guarantee for clone()---not just an A*, but
a B*. (Clone is actually a pretty poor example for this, since
the post-condition in A says that the pointer returned by
B::clone will point to a B. But the principle applies in
general.) In such cases, B is free to provide a new non-virtual
public function, which validates the new contract. Users who
know they have a B, and want to use the extended contract,
dynamic_cast their pointer to B*, and use it.
I'm still not sure why doClone is needed. Would it work if I do:
class A
{
public:
virtual A* clone() const
{
A* result = new A(*this) ;
assert( typeid( *result ) == typeid( *this ) ) ;
return result ;
}
};
class B
{
B* clone() const
{
return new B( *this ) ;
}
} ;

It would work, but what's the point. What happens if you
derived C from B, and forget to override clone. Everything
compiles fine, but anytime you clone a C, you actually get a B.
Your post condition is violated, and you never notice it.
(Actually, of course, you will notice it further down the road.
When you end up trying to understand why you have a B, when you
should have a C.)
 
J

Jess

To catch post-condition violations.


"clone" isn't virtual, because the only correct version is in
the base class. "clone" calls a virtual function, "doClone" to
do the actual work.

This is the usual C++ implementation of programming by contract.


No. The whole point of the exercise is that it is impossible to
call the virtual function without the post-condition checks.


It won't work, because clone() isn't virtual.


Right. A defines a contract. In this case, we've done nothing
in B to define a new contract, so A's contract still holds.
Which means that we start by calling a function in A, in order
to enforce the contract.

In practice, this is the usual situation. I'd guess that in
something like 90% of the cases involving inheritance, you
define a contract in a base class, and inherit, one level deep,
to provide different implementations. Practically, the only
time the user sees the derived class is when he constructs one;
all other accesses are through the base class.

There are cases, however, where the derived class wants to
provide an extended contract. Thus, for example, B might decide
to offer an extended guarantee for clone()---not just an A*, but
a B*. (Clone is actually a pretty poor example for this, since
the post-condition in A says that the pointer returned by
B::clone will point to a B. But the principle applies in
general.) In such cases, B is free to provide a new non-virtual
public function, which validates the new contract. Users who
know they have a B, and want to use the extended contract,
dynamic_cast their pointer to B*, and use it.




It would work, but what's the point. What happens if you
derived C from B, and forget to override clone. Everything
compiles fine, but anytime you clone a C, you actually get a B.
Your post condition is violated, and you never notice it.
(Actually, of course, you will notice it further down the road.
When you end up trying to understand why you have a B, when you
should have a C.)

--
James Kanze (GABI Software) email:[email protected]
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Thanks a lot for the explanations! I think there are situations where
virtual functions don't help. For example, I may have a function "f"
that takes a pointer of type A*, and it needs to know if the pointed
object is really A or B before doing anything. Suppose I don't have
access to A and B's implementations, and so I can't define any new
virtual functions. In this case, how can the function "f" know if
it's got an A object or B object?

Thanks,
Jess
 
J

James Kanze

On May 14, 5:39 pm, James Kanze <[email protected]> wrote:
Thanks a lot for the explanations! I think there are situations where
virtual functions don't help. For example, I may have a function "f"
that takes a pointer of type A*, and it needs to know if the pointed
object is really A or B before doing anything. Suppose I don't have
access to A and B's implementations, and so I can't define any new
virtual functions. In this case, how can the function "f" know if
it's got an A object or B object?

You can always try a dynamic_cast:

void
f( A* pA )
{
B* pB = dynamic_cast< B* >( pA ) ;
if ( pB != NULL ) {
// It's really a B...
} else {
// It's not a B ...
}
}
 
J

Jess

You can always try a dynamic_cast:

void
f( A* pA )
{
B* pB = dynamic_cast< B* >( pA ) ;
if ( pB != NULL ) {
// It's really a B...
} else {
// It's not a B ...
}
}

--
James Kanze (GABI Software) email:[email protected]
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


I see, great to know that, thanks!
Jess
 

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

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,821
Latest member
AleidaSchi

Latest Threads

Top