views:

228

answers:

2

I'd like to write a group of class D_I (aka I=1,2,... just I mean '_I' to be class proper name, not integer) which handle some (boolean, for example) operations F with same meaning for all classes. But also I want to operate with "sums" of object of such classes, different classes of the group might be "added" together. Mentioned operation of sum of such object depends on corresponding operation of added objects.

That's why I'm going to create common Base class B to handle operation of "summing":

class B
  {public:
      B (): LEFT(NULL), RIGHT(NULL) {};
      B (const SpBrd& S);
      B (const B& A, const B B); 
      ~B ();
           {if (LEFT != NULL) delete LEFT; // NULL if it is default B or D_I
            if (RIGHT != NULL) delete RIGHT; // NULL if it is default B or D_I
           }
      B operator + (const B& S) const;
      bool F(const Point& P) const;
           {bool b = aF(P);
            if (LEFT != NULL) b = b && LEFT->F(P);  // this is how F depends 
            if (RIGHT != NULL) b = b && RIGHT->F(P);  // of added classes
           }
   protected:
      virtual bool aF(const Point& P) const {return true;}; // Default!
      B* LEFT; // Pointer to left operand of "sum" 
      B* RIGHT; // Pointer to right operand of "sum" 
         // (since it might point to D_i class too as well)
  };

so this way derived class D is easy to write since it should only handle it's constructor and aF:

class D_I: public B
   {public:
       D() {...};
    protected:
       virtual bool aF (const Point& P) const
           {// return something here
           }
   };

The problem is: HOW TO WRITE operator + and copy constructor of class B:

   B:B (const B& S)
       {if (S.LEFT != NULL) LEFT = new B (S.LEFT)
        if (S.RIGHT != NULL) RIGHT = new B (S.RIGHT)             
       };
   B B:operator + (const B& S) const
       {SpBrd S;
        S.LEFT = new B (*this);
        S.RIGHT = new B (S);
       };

would not produce proper result because 'new B ... ' should be substituted to corresponding 'new D_I ... ' if either 'this', 'S' in in 'operator +' or 'S.LEFT' or 'S.RIGHT' in copy constructor are refers not to object of class B exactly but object of one of class D_I.

So, I can't find a way to make B::operator+ know what type sources of sum has since there might be large amount of D_I and they will be added from time to time. What should I do to write this all properly?

I can't write pars of D_I operator+ (D_J) and D_I::D_I(const D_J) for all pairs of (D_I,D_J), it is not fair!

(This would represent something like borders (surface) in space. Function F represent to be inside the border -- i.e. to check if the point is inside a space body. But bodies inside border I want to operate might be intersection (operator+) of space bodies inside other two borders. Examples: intersection of two sphere borders -- inner and outer zones -- produces spherical shell aka 3D ring; might also want to review intersection spherical border with radius R and solid angle of 10*10 degrees around pole and so on.)

+2  A: 

It's a little hard to follow exactly what you are trying to do (what is SpBrd?) but you might consider making a new virtual function like this:

class B
{
    ...
    virtual B *Clone() const { return new B(*this); }
    ...
};
class D: public B
{
    ...
    virtual D *Clone() const { return new D(*this); }
    ...
};

which can then be used like this:

S.LEFT = this->Clone();
S.RIGHT = S.Clone();
Niki Yoshiuchi
Thank you a lot, this should work fine, for sure. The only shortcoming is that I should create such copy function for all D_I classes with almost the same code! It would be great, if C++ let to use not 'new Type (arg)' but 'pointer_to_object_of_Type -> operator new (arg)', but I do not find if it is possible. Thanks again and sorry for SpBrd, of course it was B. And D_I would be SphBrd, AlphaBrd, DeltaBrd and so on...
Nick
These virtual functions should probably be const-qualified, i.e. virtual B *Copy() const
Gareth Stockwell
This pattern is commonly referred to using the verb 'clone' rather than 'copy' - naming your function Clone() will probably convey the intention more clearly. See e.g. the Java Cloneable interface: http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Cloneable.html
Gareth Stockwell
Good call gareth, on both accounts.
Niki Yoshiuchi
Also, you probably want `D::Clone` to return `D*`. This is called covariant return types and works because a `D*` can be cast to a `B*`. What is nice about it though, is that if you are working from a `D* obj = new D();` then you can write `D* newObject = obj->Clone();` and thus exploit the type information you had to its fullest.
Matthieu M.
Thank Matthieu, I copy pasted the line and thought I had edited that but apparently not.
Niki Yoshiuchi
Thank you a lot, this work fine, now I've checked. I find it to be a good solution cos I do not find retyping of Clone() function to be too hard, just a little annoying. Thanks again.
Nick
I found a way to make templates generate the clone functions for you. :-)
Omnifarious
@omnifarious can you give a description your template approach?
Gareth Stockwell
@gareth.stockwell, If you want to know how to make templates generate the clone functions for you, see my actual answer to this question, which is the answer that was accepted by the OP as well.
Omnifarious
+3  A: 

Your question is confusing, and I'm feeling rushed here, but I will say this.

Typically operators are declared outside a class as non-member functions and use member functions to accomplish their goal. This is so that both arguments to the operator can participate equally in overload resolution.

In this case it looks like you're trying to create some sort of parse tree based on C++ operators. In this case, code like this might be what you're looking for:

const B operator +(const B &a, const B &b)
{
    return B(a, b);
}

Note that this will work even if something derived from B is either operand. And if I'm right about what you're doing, you will likely have a special node type for two things added to eachother, and operator + can return that.


Now that I see your clarified version, here is a different answer.

First, I question your use of operator + in this way. operator overloading often leads to surprises for people using your class, and unless your use of operator + behaves a lot like people expect operator + to behave in general it will cause more problems that it solves.

But your actual question seems to revolve around creating copies of objects of unknown type without lots of repetitive code. When I see repetitive code, I tend to think templates. Here is some sample code that's probably a little more complex than you need that uses templates and I think solves your problem.

#include <memory>
#include <iostream>

template <class BaseType, class DerivedType>
class Cloneable {
 public:
   virtual ~Cloneable() {}

   ::std::auto_ptr<DerivedType> clone() const {
      return ::std::auto_ptr<DerivedType>(static_cast<DerivedType *>(i_clone()));
   }

 protected:
   virtual BaseType *i_clone() const {
      return new DerivedType(dynamic_cast<const DerivedType &>(*this));
   }
};

class A : public Cloneable<A, A> {
 public:
   A() {}
   A(const A &b) {
      const void * const voidb = &b;
      const void * const voidme = this;
      ::std::cerr << "Creating a copy of the A at " << voidb << " and this new copy will reside at " << voidme << "\n";
   };
   virtual ~A() {
      const void * const voidme = this;
      ::std::cerr << "Destroying the A at " << voidme << "\n";
   }
};

template <class Derived>
class B : public A, public Cloneable<A, Derived> {
 public:
   B() {}
   B(const B &b) {
      const void * const voidb = &b;
      const void * const voidme = this;
      ::std::cerr << "Creating a copy of the B at " << voidb << " and this new copy will reside at " << voidme << "\n";
   };
   virtual ~B() {
      const void * const voidme = this;
      ::std::cerr << "Destroying the B at " << voidme << "\n";
   }
   // Make sure clone can be mentioned in derived classes with no ambiguity
   using Cloneable<A, Derived>::clone;

 protected:
   // Force dominance rules to choose the correct i_clone virtual function.
   virtual A *i_clone() const {
      return Cloneable<A, Derived>::i_clone();
   }
};

class C : public B<C> {
 public:
   C() {}
   C(const C &b) {
      const void * const voidb = &b;
      const void * const voidme = this;
      ::std::cerr << "Creating a copy of the C at " << voidb << " and this new copy will reside at " << voidme << "\n";
   };
   virtual ~C() {
      const void * const voidme = this;
      ::std::cerr << "Destroying the C at " << voidme << "\n";
   }
};

class D : public B<D> {
 public:
   D() {}
   D(const D &b) {
      const void * const voidb = &b;
      const void * const voidme = this;
      ::std::cerr << "Creating a copy of the D at " << voidb << " and this new copy will reside at " << voidme << "\n";
   };
   virtual ~D() {
      const void * const voidme = this;
      ::std::cerr << "Destroying the D at " << voidme << "\n";
   }
};

int main(int argc, const char *argv[])
{
   C c;
   D d;
   ::std::auto_ptr<A> cptr(c.clone());
   ::std::auto_ptr<A> dptr(d.clone());
   cptr = dptr->clone();
   return 0;
}

I made the i_clone and clone methods so that each class would end up with a version of clone that returned a pointer to the class' own type. I also have to have the using declaration in template class B to make sure there is no ambiguity in which clone is going to be called in derived classes.

Notice how classes C and D contain no repetitive code relating to creating clones of themselves.

While a didn't know it at the time, this appears to be yet another re-invention of The Curiously Recurring Template idiom as applied to polymorphic copy construction.

Omnifarious
What's the point of returning `const B` ? I've seen it a number of times but I don't see the point since I can perfectly write `B result = a + b;`. Am I missing something here ?
Matthieu M.
I always return const X from functions to avoid allowing weird constructs like `(a + b) = 5;`.
Omnifarious
@Matthieu: it makes expressions like `a + b = c` invalid, as they are with built-in types. It's more of an issue when dereference operators return copies - you want `a[i] = b` to fail to compile, not silently assign to a temporary.
Mike Seymour
Nick
Oh, of course, now I see: each D_I has own parent B<D_I> which handle i_clone() and there is no need to copy this i_clone() because B is template! Thanks a lot, this must be perfect solution!!!
Nick
UPD: YES it works perfect!!! Thanks you a lot again!!!
Nick
The extra copy of i_clone in B is there to force the dominance rules to choose the correct virtual function.
Omnifarious