Which would you prefer: base class or two pointers?

U

Unforgiven

I have the following situation:

Given this class:
template<typename T>
class Expression {
/* omitted */
};

This is the base class for a BooleanExpression and an ArithmeticExpression.

Now I have another struct, that contains a BooleanExpression and an
ArithmeticExpression. This expression will be either Expression<int> or
Expression<string>. Which one is determined at runtime. The problem I'm
faced with is how to store them. Basically, I'm leaning toward two different
options.

1. Create a base class BaseExpression, that doesn't really do anything at
all, and have Expression inherit from it, making the other class like this:
struct MatchModifier
{
BaseExpression *test;
BaseExpression *operation;
};

I'll have a flag somewhere that I'll need to have anyway that'll tell me the
type it's going to be, so I could then dynamic_cast them whenever I need to
use them.

2. Just have two pointers, only one of which is used:
struct MatchModifier
{
BooleanExpression<int> *intTest;
BooleanExpression<std::string> *stringTest;
ArithmeticExpression *intOperation;
std::string *stringOperation;
};

As you can see here, there's not actually an
ArithmeticExpression<std::string>, it'll just be a plain string.

From my perspective, option two holds the best cards. It clearly relates the
fact that test is always a BooleanExpression and that operation is always an
ArithmeticExpression (or string constant). It also means I don't need to
create an additional class that has no real purpose, and I won't need to
create a StringConstant class or whatever that inherits from BaseExpression
to wrap the std::string. It's also more performant (I think) as the
expressions will be used often, and for option 1 that means a dynamic_cast
every single time, and option 2 just involves picking one of two pointers.
Option 2 does waste 8 bytes per MatchModifier, but that's not such a big
problem, and I could always use unions if that really becomes a concern.
Option 2 does have the distinct disadvantage that adding additional
Expression types adds more work, but at this time it doesn't seem likely
that that'll happen.

However, somehow my sense of style has a little voice in my head saying that
option 2 is wrong somehow, that there must be a more elegant solution. But
all I can come up with is solution 1.

So, which would you prefer. Or would you perhaps know a better, third
alternative?

Thanks in advance for any input.
 
D

David Lindauer

I would lean toward utilizing the C++ type matching system over doing explicit
casts if at all possible. Yes this means more work up-front and a little extra
effort when you add new types... but when you start maintaining it the extra
up-front work will pay off big-time.

David
 
S

Siemel Naran

I have the following situation:

Given this class:
template<typename T>
class Expression {
This is the base class for a BooleanExpression and an ArithmeticExpression.

struct MatchModifier
{
BaseExpression *test;
BaseExpression *operation;
};

I'll have a flag somewhere that I'll need to have anyway that'll tell me the
type it's going to be, so I could then dynamic_cast them whenever I need to
use them.
struct MatchModifier
{
BooleanExpression<int> *intTest;
BooleanExpression<std::string> *stringTest;
ArithmeticExpression *intOperation;
std::string *stringOperation;
};

Is it possible to have a 3 level system?

class Expression { ... };
class BooleanExpression : public Expression { ... };
class ArithmeticExpression : public Expression { ... };
template<typename T> class ConcreteBooleanExpression : public
BooleanExpression { ... };
template<typename T> class ConcreteArithmeticExpression : public
ArithmeticExpression { ... };

Turn MatchModifier into a class to control access to it. The public
functions will have to ensure that 'test' and 'operation' are for the same
underlying type. The destructor and copy constructor and operator== shall
delete and clone the expressions.

class MatchModifier
{
private:
BooleanExpression *d_test;
ArithmeticExpression *d_operation;
public:
MatchModifier(std::auto_ptr<BooleanExpression>,
std::auto_ptr<ArithmeticExpression>);
MatchModifier(const MatchModifier&);
MatchModifier& operator=(const MatchModifier&);
~MatchModifier();
};

MatchModifier::MatchModifier(std::auto_ptr<BooleanExpression>,
std::auto_ptr<ArithmeticExpression>)
: d_test(test.release()), d_operation(operation.release())
{
if (test->underlying_typeid() != operation->underlying_typeid())
{
throw ...;
}
}
 
U

Unforgiven

Siemel Naran said:
Is it possible to have a 3 level system?

class Expression { ... };
class BooleanExpression : public Expression { ... };
class ArithmeticExpression : public Expression { ... };
template<typename T> class ConcreteBooleanExpression : public
BooleanExpression { ... };
template<typename T> class ConcreteArithmeticExpression : public
ArithmeticExpression { ... };

I already thought about that but unfortunately no, since there's some common
code in Expression that's already templated.
 

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,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top