468,784 Members | 1,464 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Post your question to a community of 468,784 developers. It's quick & easy.

Need design advice

I'm creating a library for internal use that relies on third-party code. I
don't want clients of this library to know anything about the third party
code, when compiling or running. Generally I've been achieving this by having
an abstract base class which defines an interface, and inheriting a concrete
class which defines the implementation. Clients of the library deal only with
the base class and request objects of that type from a factory function.

This works great in most cases. The problems set in when you have operator
overloading. For example, I might have a class which defines 2-dimensional
point on the screen. I want the user to be able to use operator+, operator==,
operator=, etc... on this class.

Scanning the archives of this newsgroup it seems the big problem comes into
play with operator=, although I still see problems with the other operators as
well.

Hopefully what I'm saying is clear; if not, please let me know. :-)

What I'm trying to do is analogous to:

class Point
{
virtual void function(int x) = 0;
}

class PointImplemenation
{
virtual void function(int x);
}

except with operators:

class Point
{
virtual Point operator+(const Point& pt) = 0;
}

class PointImplementation
{
virtual PointImplementation operator+(const PointImplementation& pt);
}

I don't think this is going to work well at all, and it looks like a recipe
for many headaches in the future.

Anyone have an elegant solution for this problem? I've thought of the
possibility of using a proxy object instead of direct inheritance, although
that's not so great either, IMO.

I'm open to any ideas. :-)

Thanks!

Carl

--
Remove "NOSPAM" to reply in e-mail

Jul 19 '05 #1
5 1908
"Carl Bevil" <ca**************@yahoo.com> wrote...
I'm creating a library for internal use that relies on third-party code. I don't want clients of this library to know anything about the third party
code, when compiling or running. Generally I've been achieving this by having an abstract base class which defines an interface, and inheriting a concrete class which defines the implementation. Clients of the library deal only with the base class and request objects of that type from a factory function.

This works great in most cases. The problems set in when you have operator overloading. For example, I might have a class which defines 2-dimensional point on the screen. I want the user to be able to use operator+, operator==, operator=, etc... on this class.

Scanning the archives of this newsgroup it seems the big problem comes into play with operator=, although I still see problems with the other operators as well.

Hopefully what I'm saying is clear; if not, please let me know. :-)
After reading the rest, yes, you're unclear.

What I'm trying to do is analogous to:

class Point
{
Perhaps you need

public:

here...
virtual void function(int x) = 0;
} ;

class PointImplemenation
Shouldn't this be

class PointImplementation : public Point
{
virtual void function(int x);
} ;
except with operators:

class Point
{
public:
virtual Point operator+(const Point& pt) = 0;
}

class PointImplementation
Again, you probably meant

class PointImplementation : public Point
{
virtual PointImplementation operator+(const PointImplementation& pt);
This will not be an overrider. The return values have to be
at least covariant, which means either pointers or references
to related classes. Also, _arguments_ have to be the same.
}

I don't think this is going to work well at all, and it looks like a recipe for many headaches in the future.
Yes, it's not going to work.
Anyone have an elegant solution for this problem? I've thought of the
possibility of using a proxy object instead of direct inheritance, although that's not so great either, IMO.
That's not such a bad idea. You might want to read about the Visitor
pattern.

Also, since you've mentioned operator=, explore the necessity to add
a "clone" member function.

I'm open to any ideas. :-)


Why do you need 'operator+' redefined in the implementation class?
Isn't it simply a sum of the coordinates? Aren't the coordinates
known at the base class level?

Anyway, overloading operators is not an easy task for polymorphic
classes. The simplest answer to "how" is "don't". The biggest
problem in this situation is the "slicing" that occurs when you
return an object of the base class. Unfortunately, there is no
cure for slicing. The best thing would be to clone a point and
then use += on it (which returns a reference to the same object):

struct Point {
virtual ~Point() {}
Point* clone() const = 0;
virtual Point& operator +=(Point const&) = 0;
};

struct ParticularPoint : Point {
Point* clone() const {
return new ParticularPoint(*this); // or whatever
}

Point& operator +=(Point const& cp) {
// add coordinates of 'cp' to *this
return *this;
}
};

int main() {
ParticularPoint p1, p2;
Point* pp = p.clone();
*pp += p2;

delete pp;
}

Victor
Jul 19 '05 #2
On 05/11/2003 "Victor Bazarov" <v.********@comAcast.net> wrote:
"Carl Bevil" <ca**************@yahoo.com> wrote...

Hopefully what I'm saying is clear; if not, please let me know. :-)
After reading the rest, yes, you're unclear.


Sorry; that's what I get for coding in a newsreader....

What I'm trying to do is analogous to:

class Point
{


Perhaps you need

public:

here...


Yes, you're correct...
virtual void function(int x) = 0;
}

;

class PointImplemenation


Shouldn't this be

class PointImplementation : public Point
{
virtual void function(int x);
}

;


Yes, correct again. :-)
Anyone have an elegant solution for this problem? I've thought of the
possibility of using a proxy object instead of direct inheritance,

although
that's not so great either, IMO.


That's not such a bad idea. You might want to read about the Visitor
pattern.


Will do.
Also, since you've mentioned operator=, explore the necessity to add
a "clone" member function.

I'm open to any ideas. :-)
Why do you need 'operator+' redefined in the implementation class?
Isn't it simply a sum of the coordinates? Aren't the coordinates
known at the base class level?


No, in this case they are not. I used the Point class as an example, but in
my situation, the data is held by the derived class and is proprietary to the
third-party system I mentioned. The base class is meant only to define an
interface and not hold any data or implement any functionality.
Anyway, overloading operators is not an easy task for polymorphic
classes. The simplest answer to "how" is "don't". The biggest
problem in this situation is the "slicing" that occurs when you
return an object of the base class. Unfortunately, there is no
cure for slicing. The best thing would be to clone a point and
then use += on it (which returns a reference to the same object):

struct Point {
virtual ~Point() {}
Point* clone() const = 0;
virtual Point& operator +=(Point const&) = 0;
};
Should clone be virtual?
struct ParticularPoint : Point {
Point* clone() const {
return new ParticularPoint(*this); // or whatever
}

Point& operator +=(Point const& cp) {
// add coordinates of 'cp' to *this
return *this;
}
};

int main() {
ParticularPoint p1, p2;
Point* pp = p.clone();
*pp += p2;

delete pp;
}


I'm hoping to hide the derived class completely from clients. Thinking about
it more, this would probably require clients to always have pointers to Point
objects. Something like this:

Point* pt = Point::Create();

... and Point::Create() would be a factory which would create a ParticularPoint.

I don't think I like this after all. Perhaps I will use some sort of
proxy-ish object (I'll have a look at the Visitor pattern you mention).

Thanks, this has helped. Sorry about the code mistakes; I need more coffee...

Carl

--
Remove "NOSPAM" to reply in e-mail

Jul 19 '05 #3
"Carl Bevil" <ca**************@yahoo.com> wrote in message
news:Za********************@speakeasy.net...
I'm creating a library for internal use that relies on third-party code. I don't want clients of this library to know anything about the third party
code, when compiling or running. Generally I've been achieving this by having an abstract base class which defines an interface, and inheriting a concrete class which defines the implementation. Clients of the library deal only with the base class and request objects of that type from a factory function.

This works great in most cases. The problems set in when you have operator overloading. For example, I might have a class which defines 2-dimensional point on the screen. I want the user to be able to use operator+, operator==, operator=, etc... on this class.

Hi Car,

The two main C++ approaches to completely hide the implementation details
of a class are:
- Abstract base classes (which what you have going for)
- The pimpl idiom ( a.k.a. the Cheshire cat, and several other names).
When you do not need polymorphic behavior, the second option
is usually to be preferred.

A google search should do, but the basic idea is:
//file: Point.h

class Point {
public:
Point(); // sample constructor...
~Point();
... all public member functions here ...

//next 2 functions must be either implemented or private.
Point(Point const& orig);
Point& operator=(Point const& orig);

private:
struct Impl;
Impl* pimpl_; // as a unique data member
};
//file: Point.cpp

struct Point::Impl {
... put all the data members here ...
... utility functions and constructor if needed ...
};

Point::Point()
: pimpl_( new Impl() )
{
...
}

Point::~Point()
{
delete pimpl_;
}
If your class constructors may throw any exceptions, you should
use a smart pointer (e.g. std::auto_ptr or boost::scoped_ptr)
instead of the naked Impl* data member. This should actually
be the preferred default choice, unless you also want to
avoid exposing a smart-pointer header to your users.
( what I tend to do is put any throwing/complex
operations in the constructor of Point::Impl,
which makes it ok to keep a naked pointer ).

Regards,
Ivan
--
http://ivan.vecerina.com
Jul 19 '05 #4
Hi Ivan and NG,
If your class constructors may throw any exceptions, you should
use a smart pointer (e.g. std::auto_ptr or boost::scoped_ptr)
instead of the naked Impl* data member. This should actually
be the preferred default choice, unless you also want to
avoid exposing a smart-pointer header to your users.


Do you mean here, that I avoid all the pifalls when I throw exceptions
inside classes with pointers (allocating memory) when I just use a smart
pointer? This in fact would be very nice :-).

Coming back to the topic.

Assume my object exists e.g. a Point "p", and I have to pass it to another
class "bar". I could do this via a proxy "bar_proxy". "bar" needs "p" for
quite a long time, and I could not be sure, that "p" still exists, when
"bar" needs it. What means, I need here a kind of "delayed copying", that in
case the Point "p" is destructed, the "bar_proxy" creates an instance.

For a huge amount of Data (1000dim Point ;-) ) I assume that the class Point
itself is smart enough to do this job, what means inside my "bar_proxy" I
just create another "Point pp = p".
For small data-types (double, int,....) I just create a copy as well.

But what shall I do for "medium-sized" data-types ?

struct bar_proxy {
const Point pp; // nice for built-in types and "smart huge types"
// const Point & pp; // nicer, but if p ist destroyed it messes up my
mem.

bar_proxy(const Point& p) : pp(p);
};

Anyway, is this really a proxy or how shall I call it?

Regards,
Patrick
Jul 19 '05 #5
"Patrick Kowalzick" <Pa***************@cern.ch> wrote in message
news:bo**********@sunnews.cern.ch...
| Do you mean here, that I avoid all the pifalls when I throw exceptions
| inside classes with pointers (allocating memory) when I just use a smart
| pointer? This in fact would be very nice :-).
Well, it can help with a lot of the issues. But smart pointers
themselves aren't always trivial to use either...

| Coming back to the topic.
|
| Assume my object exists e.g. a Point "p", and I have to pass it to another
| class "bar". I could do this via a proxy "bar_proxy". "bar" needs "p" for
| quite a long time, and I could not be sure, that "p" still exists, when
| "bar" needs it. What means, I need here a kind of "delayed copying", that
in
| case the Point "p" is destructed, the "bar_proxy" creates an instance.
[...]
There are a few possible approaches.
- first, avoid copies as much as possible by passing objects
by reference when possible.
- the objects themselves may choose to implement some form of
reference counting, with or without automated COW (copy-on-write).
There are many options really.
I would suggest studying Scott Meyer's "(More) Effective C++" books
regarding these topics. You may also read about std::string (which
currently is typically implemented without reference counting,
because it is complex and inefficient in multithreaded apps).
Also, consider using boost::shared_ptr (from www.boost.org)
as a smart pointer for the contained data.

What you really need to chose AFAICT is whether a type will
automatically share its data among object copies or not.
If not, the user is responsible to take care of lifetime issues.
If yes, you need to decide wheter this sharing will be
visible to the users or not (if not, COW needs to be implemented).

| For a huge amount of Data (1000dim Point ;-) ) I assume that the class
Point
| itself is smart enough to do this job, what means inside my "bar_proxy" I
| just create another "Point pp = p".
| For small data-types (double, int,....) I just create a copy as well.
|
| But what shall I do for "medium-sized" data-types ?

There is no general rule about which approach is better.
Testing and tuning is typically required -- as for all optimizations
attempts (avoiding data copies is an optimization...).
Regards,
Ivan
--
http://ivan.vecerina.com
Jul 19 '05 #6

This discussion thread is closed

Replies have been disabled for this discussion.

Similar topics

1 post views Thread by Google Mike | last post: by
4 posts views Thread by Marquisha | last post: by
2 posts views Thread by andyjgw | last post: by
23 posts views Thread by JoeC | last post: by
2 posts views Thread by tatata9999 | last post: by
1 post views Thread by CARIGAR | last post: by
reply views Thread by zhoujie | last post: by
2 posts views Thread by Marin | last post: by
By using this site, you agree to our Privacy Policy and Terms of Use.