Interfaces without ABCs?

M

Mark P

I working frequently with similar but distinct geometric objects. For
example, a collection of Edge classes. All Edge classes should support
certain basic functions, such as getTail(), getTip, getLength, etc. The
implementation may vary however: I may store tail and tip as Point
objects or as pairs of integers, I may store length explicitly or
compute it dynamically, etc.

Now what I'd like is to be able to provide a common interface of useful
functions to any Edge implementation that will work with any other Edge
implementation. For example, bool intersects (const Edge& e) const; to
determine if two Edges intersect.

One approach is an ABC AbstractEdge from which all Edge implementations
derive. The ABC would declare the pure virtual functions getTail(),
getTip(), etc. and would define the function intersects (const
AbstractEdge& ae) const; in terms of those functions.

However I don't need any of the dynamic dispatch features of virtual
functions and would rather express this in some sort of templatized
(i.e., compile-time resolved) structure. For example, something along
these lines (though this does not work as written):

template <class EType1>
class EInterface
{
public:
template <class EType2>
bool intersects (const Etype2& e) const
{
// some definition using functions defined in the Edge classes
}
};

class Edge1 : public EInterface<Edge1>
{
public:
// define all functions needed by intersects()
};

class Edge2 : public EInterface<Edge2>
{
public:
// define all functions needed by intersects()
};

int main()
{
Edge1 e1(args);
Edge2 e2(args);
e1.intersects(e2); // doesn't work
}

The intent is that every Edge implementation defines a basic set of
functions and then can snap in a common interface of more advanced
functions that rely on those basic functions. But as I said, the above
doesn't work-- is there a way to do this?

Thanks,
Mark
 
M

Mark P

Alf said:
* Mark P:

Aluminium Beverage Can? <url: http://www.google.com/search?q=define:abc>.

OK, you probably meant an abstract base class. But I really had to google
that. It's so seldom used a description of a class that it doesn't merit an
acronym in everyday working language (except perhaps for students?).

Just "class" would do; "interface" if that's what this class is intended as.

I think I got that language from the FAQ:

http://www.parashift.com/c++-faq-lite/abcs.html

[fake code snipped]
Perhaps if could explain what you mean by "doesn't work".

After fixing all your typos it compiled, and after adding a dependency on some
derived class function, like indicated in the comments, it still compiled.

Please don't type in problem code manually: copy and paste real code.

OK, here's real code that will not compile on gcc. It complains that
getY and getX are undeclared where I've marked below (***). Any
suggestions on this one?

Thanks,
Mark

#include <iostream>
using namespace std;

template <class PType>
class PInterface
{
public:
template <class PType2>
bool matches(const PType2& p) const
{
return (getX() == p.getX() && getY() == p.getY()); // *** error
}
};

class P1 : public PInterface<P1>
{
public:
P1(int x_, int y_) : x(x_), y(y_) {}
int getX() const {return x;}
int getY() const {return y;}
private:
int x;
int y;
};

class P2
{
public:
P2(int x_, int y_) : x(x_), y(y_) {}
int getX() const {return x;}
int getY() const {return y;}
private:
int x;
int y;
};

int main()
{
P1 p(2,3);
P2 q(2,3);
P2 r(3,4);
cout << p.matches(q) << endl;
cout << p.matches(r) << endl;
}
 
A

Alf P. Steinbach

* Mark P:
#include <iostream>
using namespace std;

template <class PType>
class PInterface
{
public:
template <class PType2>
bool matches(const PType2& p) const
{
return (getX() == p.getX() && getY() == p.getY()); // *** error
}
};

Make that e.g.

// STATIC_ASSERT( IsDerivedAndBase<PType, PInterface>::yes );

template <class PType2>
bool matches(const PType2& p) const
{
PType const& derived = static_cast<PType const&>( *this );
return (derived.getX() == p.getX() && derived.getY() == p.getY());
}

Btw., all this nonsense about using three spaces per indentation level was
just a tongue-in-cheek suggestion I made at a meeting once.

I didn't mean for people to actually _do_ that! :p
 
B

BobR

Mark P wrote in message ...
OK, here's real code that will not compile on gcc. It complains that
getY and getX are undeclared where I've marked below (***). Any
suggestions on this one?

Thanks,
Mark

#include <iostream>
using namespace std;

template <class PType> class PInterface {
public:
template <class PType2> bool matches(const PType2& p) const {
return (getX() == p.getX() && getY() == p.getY()); // *** error
}
};

template <class PType> class PInterface{
public:
template <class PType2> bool matches(const PType2& p) const {
return (getX() == p.getX() && getY() == p.getY());
//***error_NOT!!<G>
}

virtual int getX() const =0; // add these pure virtuals
virtual int getY() const =0; // and try again

};
class P1 : public PInterface<P1> {
public:
P1(int x_, int y_) : x(x_), y(y_) {}
int getX() const {return x;}
int getY() const {return y;}
private:
int x;
int y;
};

class P2 {
public:
P2(int x_, int y_) : x(x_), y(y_) {}
int getX() const {return x;}
int getY() const {return y;}
private:
int x;
int y;
};

int main(){
P1 p(2,3);
P2 q(2,3);
P2 r(3,4);
cout << p.matches(q) << endl;
cout << p.matches(r) << endl;
}

// - output -
// 1
// 0
 
M

Mark P

Alf said:
* Mark P:



Make that e.g.

// STATIC_ASSERT( IsDerivedAndBase<PType, PInterface>::yes );

Can you explain what the above is? Or is that a personal notation?
template <class PType2>
bool matches(const PType2& p) const
{
PType const& derived = static_cast<PType const&>( *this );
return (derived.getX() == p.getX() && derived.getY() == p.getY());
}

Lovely! That is exactly what I wanted. Somehow I felt that there had
to be a way to explicitly use the (class) template parameter in its
definition and that was it. Thanks very much.

I work on problems where memory usage is a significant concern (think
processing VLSI designs) and this seems like a pretty handy way to avoid
the overhead of virtual functions. Is this a common pattern and are
there any obvious bad things about this pattern that would recommend
against it?

Related to this, I thought I was being clever when I extended your code
to the following in an effort to avoid having to define a const
reference in each function definition:

template <class PType>
class PInterface
{
protected:
PInterface() : me(static_cast<const PType&>(*this)) {}
public:
template <class PType2>
bool matches(const PType2& p) const
{
// const PType& me = static_cast<const PType&>(*this);
return (me.getX() == p.getX() && me.getY() == p.getY());
}
private:
const PType& me;
};

But if I'm not mistaken, this takes up an extra word for each object to
hold its "me" reference, which is no benefit at all over a virtual fcn
insofar as space is concerned. (Not to mention the possible need for a
non const reference too.) Is there any way to avoid the unsightly
static_cast in each function definition without increasing the size of a
class object?
Btw., all this nonsense about using three spaces per indentation level was
just a tongue-in-cheek suggestion I made at a meeting once.
Not sure I follow you here :) I usually use two spaces, I see you use
four. Who's using three?
I didn't mean for people to actually _do_ that! :p

Either way many thanks for your good advice,
Mark
 
M

Mark P

BobR said:
Mark P wrote in message ...



template <class PType> class PInterface{
public:
template <class PType2> bool matches(const PType2& p) const {
return (getX() == p.getX() && getY() == p.getY());
//***error_NOT!!<G>
}

virtual int getX() const =0; // add these pure virtuals
virtual int getY() const =0; // and try again

};

Agreed that would work but I want to avoid virtual functions in this case.

Thanks,
Mark
 
A

Alf P. Steinbach

* Mark P:
Can you explain what the above is? Or is that a personal notation?

It can be turned into working code by removing the '//' and providing a
definition for STATIC_ASSERT and IsDerivedAndBase. For discussion see "Modern
C++ Design" by Andrei Alexandrescu. I recommend Andrei's original design of
STATIC_ASSERT, now in the Boost library; the one he ended up with in MC++D
(the Loki library) isn't half as good, IMHO.

I work on problems where memory usage is a significant concern (think
processing VLSI designs) and this seems like a pretty handy way to avoid
the overhead of virtual functions. Is this a common pattern

Coplien called it "the curiously recurring template pattern". Also, a
template class that provides functionality in this way is often called a
"mix-in", although that indicates less dependency on the client than you have.
Andrei generalized mix-ins in various ways in MC++D, worth checking out.

and are
there any obvious bad things about this pattern that would recommend
against it?

It may duplicate code. So if you have few objects you may end up losing on
memory consumption; if you have many objects you may save memory. Debugging
and testing may be difficult because it's template based. Probably more, but
that's what strikes me first thinking of your intended application.

You may want to consider making matches() a free-standing function.

And likewise for other functions that are similarly simple and depending only
on public functionality.

Related to this, I thought I was being clever when I extended your code
to the following in an effort to avoid having to define a const
reference in each function definition:

template <class PType>
class PInterface
{
protected:
PInterface() : me(static_cast<const PType&>(*this)) {}
public:
template <class PType2>
bool matches(const PType2& p) const
{
// const PType& me = static_cast<const PType&>(*this);
return (me.getX() == p.getX() && me.getY() == p.getY());
}
private:
const PType& me;
};

But if I'm not mistaken, this takes up an extra word for each object to
hold its "me" reference, which is no benefit at all over a virtual fcn
insofar as space is concerned. (Not to mention the possible need for a
non const reference too.)

Not to mention it makes the object non-assignable.

Is there any way to avoid the unsightly
static_cast in each function definition without increasing the size of a
class object?

PType& derived() { ... }
PType const& derived() const { ... }
 

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,755
Messages
2,569,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top