tags:

views:

99

answers:

2

I have a C++ class template that makes an Array of pointers. This also gets typedefed to make Arrays of Arrays and so on:

typedef Array<Elem>           ElemArray;
typedef Array<ElemArray>      ElemArrayArray;
typedef Array<ElemArrayArray> ElemArrayArrayArray;

I would like to be able to set one leaf node from another by copying the pointer so they both refer to the same Elem.

But I also want to be able to set one Array (or Array of Arrays etc) from another. In this case I don't want to copy the pointers, I want to keep the arrays seperate and descend into each one until I get to the leaf node, at where I finally copy the pointers.

I have code that does this (below). When you set something in an Array it calls a CopyIn method to do the copying.

But because this is templated it also has to call the CopyIn method on the leaf class, which means I have to add a dummy method to every leaf class that just returns false.

I have also tried adding a flag to the template to tell it whether it contains Arrays or not, and so whether to call the CopyIn method. This works fine - the CopyIn method of the leaf nodes never gets called, but it still has to be there for the compile to work!

Is there a better way to do this?

#include <stdio.h>

class Elem {
public:
  Elem(int v) : mI(v) {}
  void Print() { printf("%d\n",mI); }
  bool CopyIn(Elem *v) { return false; }
  int mI;
};

template < typename T > class Array {
public:
  Array(int size) : mB(0), mN(size) {
    mB = new T* [size];
    for (int i=0; i<mN; i++) 
      mB[i] = new T(mN);
  }
  ~Array() {
    for (int i=0; i<mN; i++) 
      delete mB[i];
    delete [] mB;
  }
  T* Get(int i) { return mB[i]; }
  void Set(int i, T* v) { 
    if (! mB[i]->CopyIn(v) ) {
      // its not an array, so copy the pointer
      mB[i] = v; 
    }
  }
  bool CopyIn(Array<T>* v) {
    for (int i=0; i<mN; i++)  {
      if (v && i < v->mN ) {
        if ( ! mB[i]->CopyIn( v->mB[i] )) {
          // its not an array, so copy the pointer
          mB[i] = v->mB[i];
        }
      }
      else {
        mB[i] = 0;
      }
    }
    return true;  // we did the copy, no need to copy pointer
  }
  void Print() { 
    for (int i=0; i<mN; i++) {
      printf("[%d] ",i);
      mB[i]->Print();
    }
  }
 private:
  T **mB;
  int mN;
};

typedef Array<Elem>           ElemArray;
typedef Array<ElemArray>      ElemArrayArray;
typedef Array<ElemArrayArray> ElemArrayArrayArray;

int main () {
  ElemArrayArrayArray* a = new ElemArrayArrayArray(2);
  ElemArrayArrayArray* b = new ElemArrayArrayArray(3);

  // In this case I need to copy the pointer to the Elem into the ElemArrayArray
  a->Get(0)->Get(0)->Set(0, b->Get(0)->Get(0)->Get(0));

  // in this case I need go down through a and b until I get the to Elems
  //  so I can copy the pointers
  a->Set(1,b->Get(2));

  b->Get(0)->Get(0)->Get(0)->mI = 42;  // this will also set a[0,0,0]
  b->Get(2)->Get(1)->Get(1)->mI = 96;  // this will also set a[1,1,1]

  // should be 42,2, 2,2,  3,3, 3,96
  a->Print();

}
A: 

You could use template specialization to do something different with the leaf class:

template <> class Array<Elem> {
    // Define how Array<Elem> should behave
}

As others mentioned in the comments, you should really consider using operator [] instead of Get and Set methods for array indexes.

Kleist
Thanks - that got me looking at what I could do with specialization, and I think has made things a little less ugly!
Costas
A: 

I rearranged the code a bit to use template specialization, now the normal Set does a deep copy like this:

   void Set(int i, T* v) {    mB[i]->CopyIn(v);   }

but for the array that contains the leaves you have to add the specialization:

   template <> void Array<Elem>::Set(int i, Elem* v) {  SetPtr(i,v); }

Of course you have to add a new one of these for every different leaf class. At least if you forget to do this you get a compiler error because it can't find CopyIn in the Elem class.

I thought about replacing CopyIn with an overloaded assignment operator, but this would loose the compile warning if you forgot to add the specialization.

I also thought about doing operator[] instead of Get and Set (just for this example), but it seems a bit dangerous with all these pointers flying around - it's easy to write a[0] instead of (*a)[0]. Also I couldn't work out the magic needed to convert the pointer in the Array into the correct sort of reference for [] to return (anyone know how to do that?).

#include <stdio.h>

class Elem {
public:
  Elem(int v) : mI(v) {}
  void Print() { printf("%d\n",mI); }
  int mI;
};

template < typename T > class Array {
public:
  Array(int size) : mB(0), mN(size) {
    mB = new T* [size];
    for (int i=0; i<mN; i++) 
      mB[i] = new T(mN);
  }
  ~Array() {
    for (int i=0; i<mN; i++) 
      delete mB[i];
    delete [] mB;
  }
  T* Get(int i) { return (i<mN) ? mB[i] : 0; }
  void Set(int i, T* v)       { mB[i]->CopyIn(v); }
  void SetPtr(int i, Elem* v) { mB[i] = v; }
  bool CopyIn(Array<T>* v) {
    for (int i=0; i<mN; i++)  {
      if (v && i < v->mN ) {
    Set( i, v->Get(i) );
      }
      else {
        mB[i] = 0;
      }
    }
  }

  void Print() { 
    for (int i=0; i<mN; i++) {
      printf("[%d] ",i);
      mB[i]->Print();
    }
  }
 private:
  T** mB;
  int mN;
};


typedef Array<Elem>           ElemArray;
typedef Array<ElemArray>      ElemArrayArray;
typedef Array<ElemArrayArray> ElemArrayArrayArray;

template <> void Array<Elem>::Set(int i, Elem* v) {  SetPtr(i,v); }

int main () {
  ElemArrayArrayArray* a = new ElemArrayArrayArray(2);
  ElemArrayArrayArray* b = new ElemArrayArrayArray(3);

  // In this case I need to copy the pointer to the Elem into the ElemArrayArray
  a->Get(0)->Get(0)->Set(0, b->Get(0)->Get(0)->Get(0));

  // in this case I need go down through a and b until I get the to Elems
  //  so I can copy the pointers
  a->Set(1,b->Get(2));

  b->Get(0)->Get(0)->Get(0)->mI = 42;  // this will also set a[0,0,0]
  b->Get(2)->Get(1)->Get(1)->mI = 96;  // this will also set a[1,1,1]

  // should be 42,2, 2,2,  3,3, 3,96
  a->Print();

}
Costas
Ok, I figured out the [] return, I just have to do return *(mB[i]); My problem was I was trying to return something different if the bounds check failed and of course I can't I'd have to throw an exception.
Costas