[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.c++

Downcasting base-class objects to a derived-class

vsk

12/6/2008 5:28:00 PM

In my AP Comp. class, we wrote a Symbolic Algebra program in Java that
is completely based on one interface: IExpression.
I want to port my Java code to C++, for experience, and I'm having a
few issues.

C++ doesn't (to my knowledge) have an equivalent of an Interface, so
I;

class IExpression {
public:
IExpression() {};
virtual bool hasVar() ;
virtual double eval(double);
virtual string getStr();
virtual string getSmart();
virtual bool equals(IExpression&);
virtual IExpression simplify();
virtual IExpression derivative();
};

Once the "interface" or base-class was done, I wanted to implement it
with a simple class from my project: Number;

class Number : public virtual IExpression {
private:
double value;
void init();
public:
Number(double);
bool equals(Number &that);

bool hasVar();
double eval(double);
string getStr();
string getSmart();
bool equals(IExpression&);
IExpression simplify();
IExpression derivative();
};

I wrote the implementation of Number's methods in the header, and I
wont bother posting (most of) them.
The one that's giving me hell is;

bool Number::equals(IExpression &that) {
if (typeid(this) == typeid(that)) {
return this->equals(reinterpret_cast<Number&> (that));
} else {
return false;
}
}

bool Number::equals(Number &that) {
return this->value == that.value;
}

C++ has given me arcane error messages, and I don't know what I'm
doing that's so horribly incorrect.
I think it's a down-casting problem in equals(), but it's also telling
me that I have an "undefined reference to vtable".

How can I fix this?
6 Answers

Rolf Magnus

12/6/2008 5:43:00 PM

0

vsk wrote:

> In my AP Comp. class, we wrote a Symbolic Algebra program in Java that
> is completely based on one interface: IExpression.
> I want to port my Java code to C++, for experience, and I'm having a
> few issues.
>
> C++ doesn't (to my knowledge) have an equivalent of an Interface, so
> I;

Well, it has abstract base classes, with are similar, but more flexible.

> class IExpression {
> public:
> IExpression() {};

No need for that constructor. If you don't supply it, the compiler will
automatically generate one that does exactly the same.

> virtual bool hasVar() ;
> virtual double eval(double);
> virtual string getStr();
> virtual string getSmart();
> virtual bool equals(IExpression&);
> virtual IExpression simplify();
> virtual IExpression derivative();

You're missing a virtual destructor here.

> };
>
> Once the "interface" or base-class was done, I wanted to implement it
> with a simple class from my project: Number;
>
> class Number : public virtual IExpression {

Why are you using virtual inheritance?

> private:
> double value;
> void init();
> public:
> Number(double);
> bool equals(Number &that);
>
> bool hasVar();
> double eval(double);
> string getStr();
> string getSmart();
> bool equals(IExpression&);
> IExpression simplify();
> IExpression derivative();
> };
>
> I wrote the implementation of Number's methods in the header, and I
> wont bother posting (most of) them.
> The one that's giving me hell is;
>
> bool Number::equals(IExpression &that) {
> if (typeid(this) == typeid(that)) {
> return this->equals(reinterpret_cast<Number&> (that));

Never use reinterpret_cast unless you know exactly why. Here, it's the wrong
cast. You need a static_cast.

> } else {
> return false;
> }
> }
>
> bool Number::equals(Number &that) {
> return this->value == that.value;
> }
>
> C++ has given me arcane error messages, and I don't know what I'm
> doing that's so horribly incorrect.

And the messages were just "error", with no hint on what the problem
might be?

> I think it's a down-casting problem in equals(), but it's also telling
> me that I have an "undefined reference to vtable".
>
> How can I fix this?

My guess would be that you don't have an implementation for your base
class's member functions. But since it's supposed to resemble a Java
interface, you probably want to make them pure virtual.

jason.cipriani@gmail.com

12/6/2008 6:29:00 PM

0

There are a lot of issues, see below. I'm leaving a few things out,
you will want to read the C++ FAQ linked to below thoroughly, as well
as all of the other information:

On Dec 6, 12:27 pm, vsk <vmi...@gmail.com> wrote:
> In my AP Comp. class, we wrote a Symbolic Algebra program in Java that
> is completely based on one interface: IExpression.
> I want to port my Java code to C++, for experience, and I'm having a
> few issues.
>
> C++ doesn't (to my knowledge) have an equivalent of an Interface, so
> I;
>
> class IExpression {
> public:
> IExpression() {};
> virtual bool hasVar() ;
> virtual double eval(double);
> virtual string getStr();
> virtual string getSmart();
> virtual bool equals(IExpression&);
> virtual IExpression simplify();
> virtual IExpression derivative();
>
> };

You'll want to make sure you declare and define a virtual destructor
as well:

http://www.parashift.com/c++-faq-lite/virtual-functions.htm...

Also, unless your IExpression virtual functions are pure virtual
functions, you'll need to define a base implementation for the
functions (one major cause of your vtable error). Plus, pure virtual
functions will bring you closer to a Java interface. Put a "= 0" after
the declaration:

class IExpression {
public:
IExpression() {}
virtual ~IExpression() {}
virtual bool hasVar() = 0;
virtual double eval(double) = 0;
virtual string getStr() = 0;
virtual string getSmart() = 0;
virtual bool equals(IExpression&) = 0;
virtual IExpression simplify() = 0;
virtual IExpression derivative() = 0;
};

This requires a derived class to implement those functions. However,
this breaks your code. You have simplify() returning an actual
IExpression instance. That won't work for two reasons:

1. The abstract IExpression can't be instantiated on it's own.
2. This won't work anyways as an "IExpression" can only be an
IExpression, it can't be something derived from an IExpression. Your
declarations return an actual IExpression, not a reference/pointer to
something derived from an IExpression. There are other solutions, the
"simplest" way is to return a pointer to an IExpression instead, e.g.:

virtual IExpression *simplify () = 0;
virtual IExpression *derivative () = 0;

I quote "simplest" because there, you'll have to allocate new
IExpressions with new and remember to delete them:

class SomeExpression : public IExpression { ... }

IExpression * SomeExpression::simplify () {
IExpression *simplified = new SomeExpression(...); // e.g.
return simplified;
}

And then:

void f () {
SomeExpression ex;
IExpression *simplified = ex.simplify();
// use simplified, then...
delete simplified; // <-- must clean up
}

Smart pointer implementations can help you clean up automatically.
Other interface designs could remove the need to use new entirely.

Use regular virtual functions if you want to provide a default base
implementation and make it optional for derived classes to implement
the functions, e.g. with no-op base implementations:

class IExpression {
public:
IExpression() {}
virtual ~IExpression() {}
virtual bool hasVar() { }
virtual double eval(double) { }
virtual string getStr() { }
virtual string getSmart() { }
virtual bool equals(IExpression&) { }
virtual IExpression simplify() { }
virtual IExpression derivative() { }
};

> Once the "interface" or base-class was done, I wanted to implement it
> with a simple class from my project: Number;
>
> class Number : public virtual IExpression {

Virtual inheritance changes the rules a lot. C++ supports multiple
inheritance which can lead to a problem known as the "dreaded
diamond", virtual inheritance is intended to fix that. For
simplicity's sake, you don't really want to use virtual inheritance
here; your IExpression has no data members. Read this:

http://www.parashift.com/c++-faq-lite/multiple-inheri...

Specifically:

http://www.parashift.com/c++-faq-lite/multiple-inheri...#faq-25.9

So consider this instead:

class Number : public IExpression {
...
};

> private:
> double value;
> void init();
> public:
> Number(double);
> bool equals(Number &that);
> bool hasVar();
> double eval(double);
> string getStr();
> string getSmart();
> bool equals(IExpression&);
> IExpression simplify();
> IExpression derivative();
>
> };
>
> I wrote the implementation of Number's methods in the header, and I
> wont bother posting (most of) them.
> The one that's giving me hell is;
>
> bool Number::equals(IExpression &that) {
> if (typeid(this) == typeid(that)) {
> return this->equals(reinterpret_cast<Number&> (that));
> } else {
> return false;
> }

Your syntax looks correct, unless I'm missing something subtle the
compiler should be accepting this. Make sure you've #included
<typeinfo>.

However, "this" is a pointer type (it's a Number *) and "that" is a
reference type. Therefore "typeid(this)" will never equal "typeid
(that)". Even if "that" is a number, it's type is "Number &", not
"Number *". You'd want this:

if (typeid(*this) == typeid(that)) ...

But even then, it's probably safer just to use dynamic_cast. Read
this:

http://www.cplusplus.com/doc/tutorial/typeca...

You could do something like this:

bool Number::equals(IExpression &that) {
try {
Number &other = dynamic_cast<Number &>(that);
// do stuff with other as a Number
} catch (std::bad_cast&) {
return false;
}
}


> }
>
> bool Number::equals(Number &that) {
> return this->value == that.value;
>
> }
>
> C++ has given me arcane error messages, and I don't know what I'm
> doing that's so horribly incorrect.
> I think it's a down-casting problem in equals(), but it's also telling
> me that I have an "undefined reference to vtable".
>
> How can I fix this?

The major problem with the vtable is because you did not provide a
base implementation of any of your IExpression virtual functions.

By the way, you want to read this section on const-correctness:

http://www.parashift.com/c++-faq-lite/const-correc...

HTH,
Sorry if I left anything out,
Jason

Daniel T.

12/6/2008 6:48:00 PM

0

In article
<a16c084f-3adc-4fd8-9577-521ffafcceb6@f3g2000yqf.googlegroups.com>,
vsk <vminch@gmail.com> wrote:

> In my AP Comp. class, we wrote a Symbolic Algebra program in Java that
> is completely based on one interface: IExpression.
> I want to port my Java code to C++, for experience, and I'm having a
> few issues.
>
> C++ doesn't (to my knowledge) have an equivalent of an Interface, so
> I;
>
> class IExpression {
> public:
> IExpression() {};
> virtual bool hasVar() ;
> virtual double eval(double);
> virtual string getStr();
> virtual string getSmart();
> virtual bool equals(IExpression&);
> virtual IExpression simplify();
> virtual IExpression derivative();
> };
>
> Once the "interface" or base-class was done, I wanted to implement it
> with a simple class from my project: Number;
>
> class Number : public virtual IExpression {
> private:
> double value;
> void init();
> public:
> Number(double);
> bool equals(Number &that);
>
> bool hasVar();
> double eval(double);
> string getStr();
> string getSmart();
> bool equals(IExpression&);
> IExpression simplify();
> IExpression derivative();
> };
>
> I wrote the implementation of Number's methods in the header, and I
> wont bother posting (most of) them.
> The one that's giving me hell is;
>
> bool Number::equals(IExpression &that) {
> if (typeid(this) == typeid(that)) {
> return this->equals(reinterpret_cast<Number&> (that));
> } else {
> return false;
> }
> }
>
> bool Number::equals(Number &that) {
> return this->value == that.value;
> }
>
> C++ has given me arcane error messages, and I don't know what I'm
> doing that's so horribly incorrect.
> I think it's a down-casting problem in equals(), but it's also telling
> me that I have an "undefined reference to vtable".
>
> How can I fix this?

1) IExpression, in all probability, needs a virtual destructor.

2) As it stands, every member-function (method) in IExpression needs to
be defined. If you don't want to define them (because this is an
interface after all) you need to put "=0" between the ')' and the ';'.
As in:
virtual bool hasVar() = 0;

3) 'simplify()' and 'derivative()' both return IExpressions by value,
that is probably wrong. If these function are supposed to return some
sub-type of IExpression, they need to be returning by pointer or
reference. In that case, be careful not to return a pointer/reference to
a temporary variable.

4) Number::equals() should use dynamic_cast, since you mention Java, you
can think of it like this:

// Java
boolean result = false;
Number n = (Number)that;
if (n != null)
result = equals(n);
return result;

// C++
bool result = false;
Number* n = dynamic_cast<Number*>(&that);
if (n)
result = equals(*n);
return result;

The above are the outright errors in the code. Design issues include

1) const correctness

2) The fact that you are using a cast in the first place. After all,
according to the above, the number '5' is not equal to the expression '2
+ 3'. Is that really what you want?

3) C++ and Java have very different philosophies, especially when it
comes to object creation a straight port probably isn't wise. You would
learn more by attempting to re-implement the behavior of the Java
program in C++ without trying to directly port the code.

vsk

12/7/2008 5:26:00 AM

0

Thank you all very much!
Your responses are very generous, and I've manged to get my code to
compile and work.

Few things I want to respond to;

> The major problem with the vtable is because you did not provide a
> base implementation of any of your IExpression virtual functions.

I had already implemented all virtual functions in my derived-class.

> 2) The fact that you are using a cast in the first place. After all,
> according to the above, the number '5' is not equal to the expression '2
> + 3'. Is that really what you want?

No, it isn't. That's why there's an IExpression::simplify(). It
matches expressions against 36 different rules to eliminate redundancy
and make IExpressions cleaner.
An IExpression can be either a primitive, unary, or binary operation
(0, 1, or 2 arguments in the constructor). Primitives are Numbers and
Vars. Unary includes Sin, Ln, Neg, etc. Binary includes Sum, Product,
Quotient, Exponentiation etc.

> 3) C++ and Java have very different philosophies, especially when it
> comes to object creation a straight port probably isn't wise. You would
> learn more by attempting to re-implement the behavior of the Java
> program in C++ without trying to directly port the code.

That's what I meant, sorry.

I think I grasp these concepts now.
If anyone wants my Java code or the C++ code I have so far just email
me.

thanks again,
-vk

jason.cipriani@gmail.com

12/7/2008 7:18:00 AM

0

On Dec 7, 12:25 am, vsk <vmi...@gmail.com> wrote:
> Thank you all very much!
> Your responses are very generous, and I've manged to get my code to
> compile and work.
>
> Few things I want to respond to;
>
> > The major problem with the vtable is because you did not provide a
> > base implementation of any of your IExpression virtual functions.
>
> I had already implemented all virtual functions in my derived-class.

Yes, but you did not provide a *base* implementation.

In the example below, a() is a member function that can be optionally
overridden by a derived class, b() is a member function that *must* be
overridden by a derived class, and c() is a member function that can
not be overridden by a derived class:

Java, as an interface:

public interface Something {
// no such thing as a() in an interface
public void b (); // pure virtual, abstract is implicit
// no such thing as c() in an interface
}

Java, as a class:

public class Something {
public void a () { }
public abstract void b (); // pure virtual
public final void c () { }
}

C++:

class Something {
public:
virtual void a () { }
virtual void b () = 0;
void c () { }
};


The same rules apply in C++ as in Java. The a() function is just a
plain virtual function. In both Java and C++ classes a base
implementation *must* exist. The b() function is a pure virtual
function. All of your original IExpression member functions were just
virtual, not pure virtual (i.e. "abstract" in Java). Therefore, just
like plain virtual functions in Java, you must have a base
implementation. You must have both the base IExpression
implementation, and derived implementations can optionally override
them.

In Java, all functions in an interface are implicitly abstract, that
is implicitly pure virtual, meaning that this C++ class does *not*
express the same thing as a Java interface:

class IExpression {
virtual ~IExpression ();
virtual something ();
virtual another ();
};

Those are regular virtual functions. They are *not* pure virtual
functions. Therefore the implementations of those for IExpression
*must* exist, regardless of what your derived classes implement. Java
interfaces specify pure virtual functions, and so the closest thing
you can do is also use pure virtual functions in your base class:

class IExpression {
virtual ~IExpression () { } // no analog in Java interface
virtual something () = 0; // PURE virtual
virtual another () = 0; // PURE virtual
};

And now, just as with "abstract" Java class methods, and just as with
all Java interface methods, the methods something() and another() in
that C++ class are also pure virtual functions.

Read this: http://en.wikipedia.org/wiki/Virtua...


> > 2) The fact that you are using a cast in the first place. After all,
> > according to the above, the number '5' is not equal to the expression '2
> > + 3'. Is that really what you want?
>
> No, it isn't. That's why there's an IExpression::simplify(). It
> matches expressions against 36 different rules to eliminate redundancy
> and make IExpressions cleaner.

Your code didn't express that. Your function:

bool Number::equals (IExpression &that) { ... }

Always returned false if "that" wasn't a Number. Meaning for a client
to get the expected results, they'd have to be sure to always simplify
() the expression *before* calling equals(), in the hopes that "2 + 3"
would get reduced to a Number.

// I'm making up these classes and constructors:
Number n(5);
Sum sum(Number(2), Number(3));
n.equals(sum); // this would be false
n.equals(sum.simplify()); // this would be true

That's somewhat counter-intuitive, IMHO, but it might be appropriate
for your application.

> An IExpression can be either a primitive, unary, or binary operation
> (0, 1, or 2 arguments in the constructor). Primitives are Numbers and
> Vars. Unary includes Sin, Ln, Neg, etc. Binary includes Sum, Product,
> Quotient, Exponentiation etc.
>
> > 3) C++ and Java have very different philosophies, especially when it
> > comes to object creation a straight port probably isn't wise. You would
> > learn more by attempting to re-implement the behavior of the Java
> > program in C++ without trying to directly port the code.
>
> That's what I meant, sorry.
>
> I think I grasp these concepts now.

You need to read up on C++ inheritance, and on the difference between
non-virtual, virtual, and pure virtual functions in general (concepts
that apply to many languages that support inheritance). You also need
to forget about the Java "interface". While you can do conceptually
the same things in C++ that you can with interfaces in Java, the
interface construct has no direct analog in C++. The closest thing you
can get is a C++ class with nothing but pure virtual member functions
(and a virtual destructor).


HTH,
Jason

Lance Diduck

12/7/2008 6:01:00 PM

0

On Dec 6, 12:27 pm, vsk <vmi...@gmail.com> wrote:

> bool Number::equals(IExpression &that) {
>     if (typeid(this) == typeid(that)) {
>         return this->equals(reinterpret_cast<Number&> (that));
>     } else {
>         return false;
>     }
>
> }
>
> bool Number::equals(Number &that) {
>     return this->value == that.value;
>
> }

> How can I fix this?
First, lets correct the interface:

class IExpression {
public:
virtual ~IExpression() {}
virtual bool hasVar()const=0 ; //does not modify
virtual double eval(double)=0 ;
virtual string getStr()const=0 ; //does not modify
virtual string getSmart()const=0 ; //does not modify
virtual bool equals(IExpression const&)const=0 ;//does not
modify
virtual IExpression simplify()=0 ;
virtual IExpression derivative()=0 ;
//convienence operator
bool operator==(IExpression const&r)const{
return equals(r);
}
};
And modify the Number class accordingly. Now lets fix the equal
function.
First note that Number::equal is defined twice (illegal) so we need to
define only one.
Second, note that the implementation using the typeid's causes
infinite recursion.
Third, "this" is a pointer type, specifically Number const *. you are
comparing to a reference type. The condition will never be true.
Fourth, class Number virtually inherits from IExpression. This is fine
(and IHMO preferred in this case), but note that when using virtual
inheritance you need to also use dynamic_cast, and not
reinterpret_cast.
Lets combine all this together to come up with something useful:

bool Number::equals(IExpression const&that)const {
if (Number const* p=dynamic_cast<Number const*> (&that) ) {
return value == p->value;
}
return false;
}
Now, the problem is that operator== on a double is rarely what you
want. You need a tolerance of some sort. i.e.
return fabs(value - p->value)<0.001;

or whateever is appropriate for your app

Lance