inheritance, list of objects, polymorphism

V

Vladimir Jovic

Alf said:
* Make sure that objects can only be created dynamically.
The reasonable way is to make the destructor protected.

* Force use of smart pointer.
James relies on garbage collection so he probably doesn't do this,
but there are two aspects: ensuring that any newly created object's
raw pointer is immediately stored in a smart pointer, and ensuring
that only the smart pointer class has access to destroy an object.
One way to do the first it is to overload the class' allocation
function
(operator new) so that any direct 'new' expression would be overly
complicated. For C++98 then provide a macro that supplies the
requisite magic incomprehensible expression and ensures the pointer
is immediately wrapped in a smart pointer, before client code can
get at it. For C++0x I think the improved support for argument
forwarding makes the macro unnecessary. Anyways, one way to do the
second is to make destructor protected (which you'd do anyway for
the bullet point above), and grant friendship to the smart pointer.

How about having a pure virtual destructor in the base class? Can you
replace these two points with this idea?
 
J

James Kanze

* James Kanze:
* Vladimir Jovic:
James Kanze wrote:
General rule: assignment and external copy don't work well with
inheritance. (In my own code, I've gradually been introducing a
PolymorphicObject base class, with a virtual destructor and a
private copy constructor and assignment operator. With the rule
that classes designed to be used polymorphically should inherit
from PolymorphicObject.)
I do not understand why you said that "assignment and external
copy don't work well with inheritance."
Mainly it has to do with C++ variables directly being of the size
of the statically known type and directly containing an object of
that type, instead of just being pointers[1] as in Java and C# and
like languages.
When sizeof(Derived) > sizeof(Base) this means that
Base o = Derived();
performs a /slice/ of the Derived object; 'o' contains only the Base
stuff of that original object.
Not just when the sizes are different. The fact that the derived
type can be bigger than the base type (and that the compiler needs
to know the size static and member variables) may be the motivation
here, but the important point is that an object in C++ (or in Java)
cannot change its type, and that variables in C++ do have object
type (rather than reference type, as in Java). And slicing occurs
even if the sizes are the same---o has type Base.
I believe the point you raise was addressed in the immediately
following paragraph:

Yes, but I found the emphasis on sizeof misleading. The possibly
different sizes may have something to do with the original
motivation---I don't know, but the fact remains that size is
irrelevant
in the modern language.
I wouldn't rely on such a guarantee.
More to the point, you don't yourself rely on such a guarantee. :)

It depends on context.
Because nothing stops anyone from deriving a concrete class with
further dervied classes.

Well, there's always common sense:). At some point, you can't
possibly
protect against everything, and if the user is going to ignore the
basic
design of the class completely, he's hosed, and there's not much you
can do about it. In this particular case, from what has been
presented
here, Expression will in fact be an abstract base class, and there
will
only be one (or possibly two, but the intermediate level will also be
abstract) level of derivation.

In other contexts, of course, the situation might be different.

[...]
I can think of many ways that someone inadvertently declares an
automatic variable of some concrete derived class.
I think it's better to just design that possible bug vector away.
It's the distinction that you often make in this group between objects
with identity and those with just value, where the former are best
designed so that they can only be used with dynamic allocation.

Interestingly enough, I've never tried to enforce these rules. And
I've
never had problems with clients trying to allocate entity objects
other
than dynamically---by their very nature, they have an open lifetime
which doesn't coincide with any scope. (I have had cases of people
allocating value objects dynamically when they shouldn't. And I do
sometimes wonder if I shouldn't ban dynamic allocation for these. But
then, I'd have to ban it for double and int as well, and I don't know
how to do that. In the end, in such cases, the only solution is
education.)

Anyhow, my choice here is based on my experiences (i.e. the
programmers
I've worked with). Your experiences may be different, and if I did
find
myself in a context where such errors were occuring, I'd take
appropriate steps to prevent them.
That's a per class solution. And it requires one factory function per
constructor. That's sort of ugly, not to mention laborious, and since
you're designing a common PolymorphicObject base class I think you may
save a lot of work by centralizing the functionality there, -- even
though it relies on a convention, that all derived classes also
declare destructors protected (it's a shame that the accessibility
can't be inherited automatically!).

There are IMHO two separate issues. One is declaring instances on the
stack. The other is ensuring that there are no raw pointers to the
object. Essential if you hope to use shared_ptr (which is very
dangerous, and should be avoided in general), and in this case,
possible, since the basic semantics of an expression node are such
that
we can reasonably assume that derived classes will never export their
this pointer. For the second, the only solution is some sort of
factory
function. And again, for the special case of expression nodes, the
factory function can easily be a template (which still requires that
each derived class declare it friend), although I think I'd still
rather
use the boilerplate. (My editor supports copy-paste, and it's not as
if
there's any chance of the code having to be modified.)
 
J

James Kanze

Alf P. Steinbach wrote:
How about having a pure virtual destructor in the base class? Can you
replace these two points with this idea?

It doesn't help ensure the use of a smart pointer, and it doesn't
prevent allocation on the stack of a derived class. Since in
practice,
there will be pure virtual functions in the base class anyway, I don't
think it buys you anything.
 
J

John H.

First, I wish to thank everyone for their support.

[...]  Your lists are of type Expression,
so only Expression objects will live in the list.  When you push_back
onto the list, Expression objects are created.

Could you please elaborate a little bit on that ?

I have taken your code and added a copy constructor to Expression. If
you trace through the prints you will see that it gets called when
push_back is called. Also notice that with this copy constructor,
only the nname information can be copied into the new Expression
objects. I think that in your example, the compiler is creating its
own copy constructor that is doing something similar to the one I
provided.
Feel free to ask if you have any more questions.

#include <iostream>
#include <list>
using namespace std;

class Expression {
public:
string nname;
Expression ()
{ cout << "constructor Expression, no name provided" << endl;}
Expression (string n)
{ cout << "constructor Expression, name " << n << endl;
nname = n; }
virtual string name()
{ cout << "method Expression::name"<< endl; return nname; }

// Copy constructor:
Expression (const Expression &expression)
{ cout << "constructor Expression, expression provided" << endl;
nname = expression.nname;}
};

class ExpressionProduct : public Expression {
public:
list<Expression> factors;
ExpressionProduct ()
{cout << "constructor ExpressionProduct, no arguments" << endl;}
ExpressionProduct (const Expression &f)
{ cout << "constructor ExpressionProduct, one argument" << endl;
factors.push_back(f); }
ExpressionProduct (const Expression &f, const Expression &g)
{ cout << "constructor ExpressionProduct, two arguments" << endl;
factors.push_back(f);
factors.push_back(g); }
string name()
{ cout << "method ExpressionProduct::name"<< endl;
list<Expression>::iterator i=factors.begin();
if (i==factors.end()) return "1";
string n;
for (;i!=factors.end();i++) n = n + i->name();
return n; }

};

class ExpressionSum : public Expression {
public:
list<Expression> terms;
ExpressionSum ()
{cout << "constructor ExpressionSum, no arguments" << endl;}
void add (Expression &m)
{ cout << "adding" << endl; terms.push_back(m); }
string name()
{ cout << "method ExpressionSum::name"<< endl;
list<Expression>::iterator i=terms.begin();
if (i==terms.end()) return "0";
string n=i->name();
i++ ;
for (;i!=terms.end();i++)
n = n + '+' + i->name();
return n; }

};

int main () {
Expression x("x"), y("y"), z("z");
ExpressionProduct xy(x,y), yz(y,z);
cout << xy.name() << endl;
ExpressionSum p;
p.add(xy); p.add(yz); p.add(z);
cout << p.name() << endl;
return 0;
}
 
J

James Kanze

"barbaros" <[email protected]> ha scritto nel messaggionews:f2890a48-ed1d-4451-86e6-12c5fb13ccc5@a21g2000yqc.googlegroups.com...
i not understand much but this seems ok

No it's not. Not from a design point, anyway, and it doesn't begin to
fulfill the requirements.

What he basically needs is something along the lines of:

// Interface...
class Expression
{
Expression( Expression const& );
Expression& operator=( Expression const& );
public:
virtual ~Expression() {}
virtual double value() const = 0;
// other functions...?
};

class OneOperandExpression : public Expression
{
protected:
OneOperandExpression( Expression const* op )
: m_op( op )
{
}
Expression const* m_op;
public:
virtual ~OneOperandExpression() { delete m_op; }
};

class TwoOperandExpression : public Expression
{
protected:
TwoOperandExpression( Expression const* lhs, Expression const*
rhs )
: m_lhs( lhs )
, m_rhs( rhs )
{
}
Expression* m_lhs;
Expression* m_rhs;

public:
virtual ~TwoOperandExpression() { delete m_lhs; delete
m_rhs; }
};

class ConstantExpression : public Expression
{
double m_value;
public:
ConstantExpression( double value )
: m_value( value )
{
}

virtual double value() const
{
return m_value;
}
};

class NegExpression : public OneOperandExpression
{
public:
NegExpression( Expression const* op )
: OneOperandExpression( op )
{
}

virtual double value() const
{
return - m_op->value();
}
};

class AddExpression : public TwoOperandExpression
{
public:
AddExpression( Expression const* lhs, Expression const* rhs )
: TwoOperandExpression( lhs, rhs )
{
}
virtual double value() const
{
return lhs->value() + rhs->value();
}
};

Systematic use of boost::shared_ptr< Expression const > in place of
Expression const* would simplify things somewhat. Providing static
factory functions for each type, with the actual constructors private,
would ensure that all instances are dynamically allocated (and
immediately put under control of a shared_ptr, if shared_ptr is being
used). Using some sort of "designated" memory management can make
things significantly safer and faster, at the cost of some additional
complexity, including at the client level.
 
B

barbaros

I have taken your code and added a copy constructor to Expression.  If
you trace through the prints you will see that it gets called when
push_back is called.  Also notice that with this copy constructor,
only the nname information can be copied into the new Expression
objects.  I think that in your example, the compiler is creating its
own copy constructor that is doing something similar to the one I
provided.

Yes, now I understand. Thank you for your clear explanation.
 
J

James Kanze

so what there is in the class "Expression" there are only
functions or there is something, a pointer, a string type, a
double etc at last some data

There's nothing. Not even the functions. It's an interface;
you never instantiate it.
Error E2251 str0.cpp 24: Cannot find default constructor to initialize base
class 'Express
ion' in function OneOperandExpression::OneOperandExpression(const Expression *)

I say, "something like". I typed the code in off the top of my
head. I forgot that yes, since you've declared a constructor
(the copy constructor), you no longer get the default
constructor. So you have to provide it as well (protected, and
with an empty body).
Error E2034 str0.cpp 36: Cannot convert 'const Expression *' to 'Expression *'
in function
TwoOperandExpression::TwoOperandExpression(const Expression *,const Expression
*)

A simple typo. There's a const missing somewhere.
Error E2034 str0.cpp 37: Cannot convert 'const Expression *' to 'Expression *'
in function
TwoOperandExpression::TwoOperandExpression(const Expression *,const Expression
*)
Error E2251 str0.cpp 38: Cannot find default constructor to initialize base
class 'Express
ion' in function TwoOperandExpression::TwoOperandExpression(const Expression
*,const Expre
ssion *)
Error E2251 str0.cpp 54: Cannot find default constructor to initialize base
class 'Express
ion' in function ConstantExpression::ConstantExpression(double)
Error E2451 str0.cpp 86: Undefined symbol 'lhs' in function
AddExpression::value() const
Error E2451 str0.cpp 86: Undefined symbol 'rhs' in function
AddExpression::value() const
*** 7 errors in Compile ***
this is what seems at last compile
but i'm sure it not like or i don't like it
#include <iostream.h>
#include <list.h>
#include <string.h>
#include <stdlib.h>
using namespace std;
// Interface...
class Expression{
public:
Expression( ){st="";}
Expression( Expression& m){st=m.st;}
Expression& operator=( Expression& m )
{ st=m.st; return m;}

Copy construction and assignment were private for a reason. And
not implemented for the same reason.
double f(Expression& m){return atof(m.st.c_str());}

double value(){return f(*this);}
~Expression() {}
string st;
};

What is the string there for? Why are any of the constructors
public? Just add an empty default constructor (protected,
although it doesn't really matter).

And the whole point is that value is a pure virtual function
here.
class OneOperandExpression
{public:
OneOperandExpression(){m_op=0;}
OneOperandExpression(Expression* op){m_op=op;}

Why the assignments? Use initialization (as I initially did).
Expression* m_op;

And make this a pointer to const. If I can trust your error
messages, that's all that's required.
class TwoOperandExpression{
public:
TwoOperandExpression(Expression* lhs, Expression* rhs )
{ m_lhs = lhs; m_rhs=rhs;}
Expression* m_lhs;
Expression* m_rhs;

As above: use initialization, and make the members pointer to
const.
class ConstantExpression{
public:
ConstantExpression(double value){ m_value=value;}
virtual double value() {return m_value;}
double m_value;

Here, m_value should be private.
class NegExpression : public OneOperandExpression{
public:
NegExpression(){OneOperandExpression();}

What on earth are you doing here? First, it makes no sense for
NegExpression to have a default constructor, since a negation
operator always has an operand. And what is the purpose of
creating the temporary OneOperandExpression in the constructor
(which wouldn't have been possible with my code, since
OneOperandExpression was an abstract type).
NegExpression(Expression* op){m_op=op;}
double value(){return -(m_op->value());}

class AddExpression : public TwoOperandExpression{
public:
AddExpression(Expression* lhs, Expression* rhs )
:TwoOperandExpression( lhs, rhs ){;}
double value()
{return m_lhs->value()+m_rhs->value();}
};

You seem to put in the virtual keyword more or less at random.
int main(void)
{
return 0;
}

Somehow, I don't think you've understood what is going on.
 
J

James Kanze

"James Kanze" <[email protected]> ha scritto nel messaggionews:d3cde4c3-dd7d-473b-b71d-b82fcef30e26@o28g2000yqh.googlegroups.com...
yes i'm not so smart, but how to use all this?

It's not a question of "smart", it's a question of what you've
learned (or haven't learned). It this case, given your comments
and your code, I suspect that there's a lot of basic C++ and OO
to which you haven't been exposed. Enough that it can't be
addressed in a simple answer here---a good book is in order. (I
would suggest Stroustrup's most recent book for starters. It's
one of the best tutorial texts for "programming" that I've seen
to date: it doesn't teach C++ as such, it teaches programming,
using C++ as a major tool.)

Until you've actually learned about inheritance, abstract base
classes, and how and why they are used, you won't be able to
make sense out of my code.
why declare all distructors "virtual" ?

Because once I've constructed an object, I will only use it
through a pointer to the base class. And deleting an object
through a pointer to the base class is only legal if the
destructor is virtual.
why use all that "const"?

Because I felt like it:).

Seriously, as designed, I expect all of the derived classes to
be immutable, so const correctness says that all of my functions
should be const, and that all of my pointers be const. In some
cases, const correctness is important, but not when all objects
must be allocated on the heap; I could just as easily dropped
all of the const, with no significant difference. What doesn't
work is mixing the two approaches: either everything const
correct, or nothing.
 
T

tanix

It's not a question of "smart", it's a question of what you've
learned (or haven't learned). It this case, given your comments
and your code, I suspect that there's a lot of basic C++ and OO
to which you haven't been exposed. Enough that it can't be
addressed in a simple answer here---a good book is in order.

"Good book" defined as?
(I
would suggest Stroustrup's most recent book for starters.

The last time I read a Stroustrup's book, it was about the
worst reading I ever had. Hopefully, he learned something
after more than a generation of experience.

But I doubt he can write a book that is brilliant.

When I talked to him at Ruben Engineering in Cambridge Mass.,
what I saw is a conman in front of me. Because I asked him
about the most appropriate question there is, at least at
that time, and that is:

why did you implement C++ as a C preprocessor?

He said no, it is not a C preprocessor, which is a lie,
because it was. And all the objects were patched during
the link phase in the executable, which is totally off
the wall. But fine. We can understand that considering
the state of the art at the moment.

Now, what IS C++?

Well, nothing more than a wrapper for C.
What are the C++ objects?
Well, nothing more than C structures.
Dig?

So, where is that object oriented magic?
Well, in converting the C++ code into C code essentially,
no matter what "expert" is going to blabber what.
It's
one of the best tutorial texts for "programming" that I've seen
to date:

Poor you.

Have you ever read Osho?
Compared to Stroustrup, it is like listening to angels
sing in heaven.
it doesn't teach C++ as such, it teaches programming,
using C++ as a major tool.)

C++ is lame. No questions about it.
I do agree with Java chief architect on that one.
Unless you know Java, you'll never be able to understand
how screwed up C++ is.

After working in Java for a few years, I can not even go back
to C++ even though my main program is written in C++.
It just rips my eyes to even look at that code.
So ungly the syntax and the whole trip is.
Until you've actually learned about inheritance, abstract base
classes,

Screw those.
What do you need them for?
and how and why they are used, you won't be able to
make sense out of my code.
:--}

Don't listen to all these "experts".
There are some things you are going to learn.
Some of it does make sense, like things of private/protected/
public level. But most of it is just mental masturbation
that does not buy you anything. Their programs still suck.
Because once I've constructed an object, I will only use it
through a pointer to the base class. And deleting an object
through a pointer to the base class is only legal if the
destructor is virtual.

Because these people keep inventing these things that are
in effect meaningless in the scheme of things.
Yes, they will be telling you: oh, but now we can protect
the const and guarantee they are not going to be modified,
and on and on and on.

But the end result of all these great "improvements" is what?

Well, C++ is history.

What we have now is Python, Ruby, PHP, stinky Javascript, CSS,
HTML and things like that, that constitute a majority of what
is happening in the world right now. Const or template or
generics or you name it is nothing more than a mental
masturbation excersize.

What is happening is portability, dynamic scoping and
interpetation versus compilation.

They can design or "improve" anything they want.
Lil does it matter in the scheme of things.
Because I felt like it:).

Well, at least you are honest in saying it is utterly
meaningless.

Anyway, enough for now.
Seriously, as designed, I expect all of the derived classes to
be immutable, so const correctness says that all of my functions
should be const, and that all of my pointers be const. In some
cases, const correctness is important, but not when all objects
must be allocated on the heap; I could just as easily dropped
all of the const, with no significant difference. What doesn't
work is mixing the two approaches: either everything const
correct, or nothing.

--
Programmer's Goldmine collections:

http://preciseinfo.org

Tens of thousands of code examples and expert discussions on
C++, MFC, VC, ATL, STL, templates, Java, Python, Javascript,
organized by major topics of language, tools, methods, techniques.
 

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,774
Messages
2,569,598
Members
45,149
Latest member
Vinay Kumar Nevatia0
Top