views:

77

answers:

4

I have an existing application in C++ with a custom ArrayBase class that manages storage and access to a contiguously allocated region of memory. I have a separate ItrBase class that is used to access data in that ArrayBase. ArrayBase has a createItr() function that currently returns an ItrBase object.

I need to extend ArrayBase to use multiple memory allocations instead of one contiguous one. I have created an EnhancedArray class to do that. For this EnhancedArray to be compatible with the existing application, it's createItr() function must return something that works with the new multiple memory allocations.

So, I have created a derived EnhanceItr class to do this. My problem is I can't figure out a way for hundreds of code occurrences like this:

ItrBase anIterator = anArray.createItr();
...
double x = anIterator.getData();

to use the EhancedItr's getData() function when anArray is an EnhancedArray.

Here is a simple application illustrating my basic arrangement.

#include <iostream>
using namespace std;

class ItrBase {
public:
 ItrBase() { cout << "ItrBase constructor.\n"; };
 ~ItrBase() { cout << "ItrBase destructor.\n"; };
 virtual int vfunc() {return 1;};
};


class EnhancedItr : public ItrBase {
public:
 EnhancedItr() { cout << "EnhancedItr constructor.\n"; };
 ~EnhancedItr() { cout << "EnhancedItr destructor.\n"; };
 int vfunc() {return 0;};
};


class ArrayBase {
public:
 ArrayBase() { cout << "ArrayBase constructor.\n"; };
 ~ArrayBase() { cout << "ArrayBase destructor.\n"; };
 virtual ItrBase & createItr() {cout << "in AB's createItr()\n"; return *new ItrBase(); };
};


class EnhancedArray : public ArrayBase {
public:
 EnhancedArray() { cout << "EnhancedArray constructor.\n"; };
 ~EnhancedArray() { cout << "EnhancedArray destructor.\n"; };
 EnhancedItr & createItr() {cout << "in EA's createItr()\n"; return *new EnhancedItr(); };
};


int main()
{
 ArrayBase ab;
 EnhancedArray ea;

 ItrBase itr = ab.createItr();  
 ItrBase eitr = ea.createItr();  //EnhancedItr assigned to ItrBase

 cout << "ArrayBase's Itr .vfunc(): " << itr.vfunc() <<std::endl;
 cout << "EnhancedArray's Itr .vfunc(): " << eitr.vfunc() <<std::endl;
 return 0;
}

Both calls to vfunc() above return 1, when I want the second call to return 0.

In main(), I know that if I change the ItrBase types to ItrBase &'s, I do get the desired return types, but then I am modifying my 'existing' code in hundreds of areas, and the destructors for the Iterators are not called.

Is there another strategy that I am not seeing?

Thanks.

+2  A: 

Sure, if you're allowed to rewrite ItrBase, then you can use delegation to pass all function calls through to an implementation class, which you hold by pointer or reference so that polymorphism is in effect. This would look a lot like pimpl. And the callers would not have to be written at all, only recompiled.

EDIT: code for those not familiar with pimpl.

struct ItrBase
{
  struct ItrImpl
  {
    virtual ~ItrImpl(){}
    virtual int vfunc() = 0;
  };

  ItrBase(ItrImpl peer) : m_peer(peer) { cout << "ItrBase constructor.\n"; }
  ~ItrBase() { cout << "ItrBase destructor.\n"; }
  int vfunc() { return m_peer->vfunc(); }
private:
  const unique_ptr<ItrImpl> m_peer;
};

class ArrayBase
{
  struct ItrImpl : public ItrBase::ItrImpl
  {
    virtual int vfunc() { return 0; }
  };

public:
  ArrayBase() { cout << "ArrayBase constructor.\n"; };
  ~ArrayBase() { cout << "ArrayBase destructor.\n"; };
  virtual ItrBase createItr() { cout << "in AB's createItr()\n"; return ItrBase(new ItrImpl); };
};

class EnhancedArray : public ArrayBase
{
  struct ItrImpl : public ItrBase::ItrImpl
  {
    virtual int vfunc() { return 1; }
  };

public:
  EnhancedArray() { cout << "EnhancedArray constructor.\n"; };
  ~EnhancedArray() { cout << "EnhancedArray destructor.\n"; };
  virtual ItrBase createItr() { cout << "in EA's createItr()\n"; return ItrBase(new ItrImpl); };
};
Ben Voigt
I can rewrite ItrBase. So if I am understanding correctly, I would not have a class derived from ItrBase, just a different ItrBase that behaves differently based on the data type of the caller? I'm not familiar with pimpl, so I will read up on it.
NoahR
I'll add some code to make it clearer.
Ben Voigt
Thanks a lot. You don't mean for the ItrImpl() constructor to be declared virtual do you?
NoahR
No I didn't (and obviously this example is not compile-tested). Constructors can't be virtual.
Ben Voigt
Is using a unique_ptr integral to this solution? I haven't used it before and g++ isn't parsing it as a type right now.
NoahR
What version of g++ do you have? The very newest versions should include `std::unique_ptr`, with older versions you can use `boost::unique_ptr` or `std::auto_ptr` and provide copy-constructor and assignment operator that steals ownership of m_peer.
Ben Voigt
oops... I did intend for it to be virtual, because I also intended for it to be a destructor, not a constructor. Re-fixed.
Ben Voigt
A: 

You're running into a problem called slicing: createItr returns a reference, and then you're copying that into an ItrBase by-value. It's as if you did something like this:

EnhancedItr itr1 = ...;
BaseItr itr2 = itr1;  // copy by-value
cout << itr2.vfunc();  // prints 1, not 0

You're also leaking memory: createItr returns a newly allocated object, but you're never deleting it. This is very bad, especially since you'd expect array iterators to be used frequently.

Adam Rosenfield
A: 

completely different thing you can do is use,

BOOST_AUTO(iterator, array);

and let compiler figure out return type.

BOOST_AUTO

aaa
A: 

Not being up-to-date with the Standard Library, I could not use the unique_ptr<> implementation suggested by Ben Voigt. (version >=4.3) I believe I have taken his concept and implemented it with basic pointers instead. Noting, however, that this implementation is not exception-safe. ItrImpl objects could be left undeleted.

Here's my code. Too bad createItr() has to return a ItrBase object rather than a pointer, otherwise I think I could have gotten auto_ptr<> to work. Output during program execution shows that ~ItrBase() is called only once for each instance, but I am suprised it is not called also during the object return from createItr(). Return value optimization?

#include <iostream>
using namespace std;

struct ItrBase
{
    struct ItrImpl
    {
        virtual ~ItrImpl(){};
        virtual int vfunc() const = 0;
    };
    ItrBase(ItrImpl* peer) : m_peer(peer) { cout << "ItrBase constructor.\n"; };
    ~ItrBase() { cout << "ItrBase destructor. \n"; delete m_peer; };
    int getData() const { return m_peer->vfunc(); };

private:
    ItrImpl* const  m_peer;
};

class ArrayBase
{
    struct ItrImpl : public ItrBase::ItrImpl
    {
        virtual int vfunc() const { return 0; };
    };

public:
    ArrayBase() { cout << "ArrayBase constructor.\n"; };
    ~ArrayBase() { cout << "ArrayBase destructor.\n"; };
    virtual ItrBase createItr() { cout << "in AB's createItr()\n"; return ItrBase(new ItrImpl); };
};

class EnhancedArray : public ArrayBase
{
    struct ItrImpl : public ItrBase::ItrImpl
    {
        virtual int vfunc() const { return 1; };
    };

public:
    EnhancedArray() { cout << "EnhancedArray constructor.\n"; };
    ~EnhancedArray() { cout << "EnhancedArray destructor.\n"; };
    virtual ItrBase createItr() { cout << "in EA's createItr()\n"; return ItrBase(new ItrImpl); };
};

int main()
{
    ArrayBase ab;
    EnhancedArray ea;

    ItrBase itr = ab.createItr();  
    ItrBase eitr = ea.createItr();  //EnhancedItr assigned to ItrBase


    cout << "ArrayBase's Itr .vfunc(): " << itr.getData() <<std::endl;
    cout << "EnhancedArray's Itr .vfunc(): " << eitr.getData() <<std::endl;

    return 0;
}
NoahR