I have been using C# for a while now, and going back to C++ is a headache. I am trying to get some of my practices from C# with me to C++, but I am finding some resistance and I would be glad to accept your help.
I would like to expose an iterator for a class like this:
template <class T>
class MyContainer
{
public:
// Here is the problem:
// typedef for MyIterator without exposing std::vector publicly?
MyIterator Begin() { return mHiddenContainerImpl.begin(); }
MyIterator End() { return mHiddenContainerImpl.end(); }
private:
std::vector<T> mHiddenContainerImpl;
};
Am I trying at something that isn't a problem? Should I just typedef std::vector< T >::iterator? I am hoping on just depending on the iterator, not the implementing container...
You may find the following article interesting as it addresses exactly the problem you have posted: On the Tension Between Object-Oriented and Generic Programming in C++ and What Type Erasure Can Do About It
I have done the following before so that I got an iterator that was independent of the container. This may have been overkill since I could also have used an API where the caller passes in a vector<T*>&
that should be populated with all the elements and then the caller can just iterate from the vector directly.
template <class T>
class IterImpl
{
public:
virtual T* next() = 0;
};
template <class T>
class Iter
{
public:
Iter( IterImpl<T>* pImpl ):mpImpl(pImpl) {};
Iter( Iter<T>& rIter ):mpImpl(pImpl)
{
rIter.mpImpl = 0; // take ownership
}
~Iter() {
delete mpImpl; // does nothing if it is 0
}
T* next() {
return mpImpl->next();
}
private:
IterImpl<T>* mpImpl;
};
template <class C, class T>
class IterImplStl : public IterImpl<T>
{
public:
IterImplStl( C& rC )
:mrC( rC ),
curr( rC.begin() )
{}
virtual T* next()
{
if ( curr == mrC.end() ) return 0;
typename T* pResult = &*curr;
++curr;
return pResult;
}
private:
C& mrC;
typename C::iterator curr;
};
class Widget;
// in the base clase we do not need to include widget
class TestBase
{
public:
virtual Iter<Widget> getIter() = 0;
};
#include <vector>
class Widget
{
public:
int px;
int py;
};
class Test : public TestBase
{
public:
typedef std::vector<Widget> WidgetVec;
virtual Iter<Widget> getIter() {
return Iter<Widget>( new IterImplStl<WidgetVec, Widget>( mVec ) );
}
void add( int px, int py )
{
mVec.push_back( Widget() );
mVec.back().px = px;
mVec.back().py = py;
}
private:
WidgetVec mVec;
};
void testFn()
{
Test t;
t.add( 3, 4 );
t.add( 2, 5 );
TestBase* tB = &t;
Iter<Widget> iter = tB->getIter();
Widget* pW;
while ( pW = iter.next() )
{
std::cout << "px: " << pW->px << " py: " << pW->py << std::endl;
}
}
This should do what you want:
typedef typename std::vector<T>::iterator MyIterator;
From Accelerated C++:
Whenever you have a type, such as vector<T>
, that depends on a template parameter, and you want to use a member of that type, such as size_type
, that is itself a type, you must precede the entire name by typename
to let the implementation know to treat the name as a type.
I am unsure about what you mean by "not exposing std::vector publicly" but indeed, you can just define your typedef like that:
typedef typename std::vector<T>::iterator iterator;
typedef typename std::vector<T>::const_iterator const_iterator; // To work with constant references
You will be able to change these typedefs later without the user noticing anything ...
By the way, it is considered good practice to also expose a few other types if you want your class to behave as a container:
typedef typename std::vector<T>::size_type size_type;
typedef typename std::vector<T>::difference_type difference_type;
typedef typename std::vector<T>::pointer pointer;
typedef typename std::vector<T>::reference reference;
And if needed by your class:
typedef typename std::vector<T>::const_pointer const_pointer;
typedef typename std::vector<T>::const_reference const_reference;
You'll find the meaning of all these typedef's here: STL documentation on vectors
Edit: Added the typename
as suggested in the comments