Skip to main content

Going up step by step



Have you ever wanted to be able to iterate through your own class as if it was a cool STL container?
Do something like this:

class MyStorage : Storate<float>
{
public:
...
};
...
{
...
for (auto& item : myStorageInstance)
{
item.UpdateImportantInfo();
}
...
}
view raw ICameToIterate hosted with ❤ by GitHub
Then it there is a way to integrate your class into STL and get powerful support. In order to be able to provide interface to for each loop you need to support in your custom classes set of operations such as:

class MyStorage : Storate<float>
{
public:
...
class Iterator {
MyIterator(A::SomeClass* ptr)
{
...
bool operator==(const Iterator& itr)...
bool operator!=(const Iterator& itr)...
Iterator operator++()... -- post-incr
Iterator operator++(int)... -- pre-incr
...
}
};
Iterator begin();
Iterator end();
...
};
view raw AndChewGum hosted with ❤ by GitHub
After giving it a good time to fix all the bugs and understand them all as single piece you will notice what really for each loop needs to be able to iterate through a custom object.

Here a code example. Simple one.

#include <string>
#include <iostream>
template <typename T>
class ListNode
{
ListNode* _next = nullptr;
T _value;
public:
ListNode(T value) : _value(value) {}
void SetNext(ListNode* node) {
_next = node;
}
ListNode* GetNext() const { return _next; }
std::string GetValue() {
return _value;
}
class Iterator
{
public:
Iterator(ListNode* ptr) : ptr_(ptr) { }
Iterator operator++() { Iterator i = *this; ptr_ = ptr_->GetNext(); return i; }
ListNode& operator*() { return *ptr_; }
ListNode* operator->() { return ptr_; }
bool operator==(const Iterator& rhs) { return ptr_ == rhs.ptr_; }
bool operator!=(const Iterator& rhs) { return ptr_ != rhs.ptr_; }
private:
ListNode* ptr_;
};
Iterator begin() { return Iterator(this); }
Iterator end() { return Iterator(nullptr); }
};
int main(int argc, char** argv)
{
ListNode<std::string> node("I've");
node.SetNext(new ListNode<std::string>("got the"));
node.GetNext()->SetNext(new ListNode<std::string>("power"));
for (auto itr : node)
std::cout << itr.GetValue() << " ";
std::cout << "\n";
return 0;
}
If you have a big class on your hands that stores all the data and you want to implement a more secure and encapsulated class you can implement a set of Wrappers and Iterators to iterate all of those edges of your class.

#include <iostream>
#include <algorithm>
#include <vector>
template <typename T>
using Storage = std::vector<T>;
class ValuesWrapper;
class BigBlobData
{
friend ValuesWrapper;
Storage<int> _someValues;
Storage<float> _somePoints;
Storage<std::string> _someStrings;
public:
void SetPoints(Storage<float> someValues) {
std::merge(someValues.begin(), someValues.end(),
_someValues.begin(), _someValues.end(),
std::back_inserter(_someValues));
}
};
class ValuesWrapper {
const Storage<int>& _someValues;
public:
ValuesWrapper(const BigBlobData& blob) : _someValues(blob._someValues)
{
}
class Iterator
{
const int* _data;
public:
Iterator(const int* data) : _data(data) {}
Iterator operator++()
{
_data++;
return {_data};
}
bool operator==(const Iterator& rhs)
{
return _data == rhs._data;
}
bool operator!=(const Iterator& rhs)
{
return !(_data == rhs._data);
}
int operator*()
{
return *_data;
}
};
Iterator begin() { return Iterator(_someValues.data()); }
Iterator end() { return Iterator(_someValues.data() + _someValues.size()); }
};
int main(int argc, char** argv)
{
BigBlobData blob;
blob.SetPoints({1, 3, 4, 6, 7 , 8, 11, 13, 15, 16, 19, 20});
blob.SetPoints({32, 34, 42, 53, 67, 71});
for (int value : ValuesWrapper(blob))
std::cout << value << " ";
std::cout << "\n";
return 0;
}
view raw AndImOutOfGum hosted with ❤ by GitHub
This code sample is a bit crazy. But it shows how you can separate iteration routine and the rest of class logic. Controlling complexity is a goal of software architecture. Patterns, SOLID and other instruments are there only cause of some bright minds wanted to show their jobs on that topic for other people to use it.

Iterators are neat concept separating STL algorithms and containers. Watching on Iterator pattern from another angle reveals different way to look at it's place in STL. Since Iterators usually implemented inside of containers they as glue connects two islands of the STL archipelago.

It's better to think about this relations as if there is a strictly established policy of data translation. Such high level of abstraction model let us see what the idea behind design of STL, as well as start to see other libraries on such level.

Iterate and prosper.

Popular posts from this blog

Constness of an object

Defining a function you might want to make some of it's arguments to be constant to avoid unnecessary copying or have a bit more strict interface. There is another way to use constness - there we meet constant class members.

Move semantics

For a long time in C++ was no fast way to deal with return value. They were copied and there was no hope to change it. Some have used return parameters in argument list like: void GetObjectsInArea(Area exactArea, ObjectList& objList); To avoid unnecessary copies and speed up the execution developers were doing a lot of work. Eventually when optimized code were becoming unreadable the developers would abandon it and the code would transform into legacy code at some point.

Templates and how to fold them

Variadic templates appeared in C++11 to cover such cases when you would have a template functions that could have a numerous members of different types. Doesn't it remind you of variadic functions that uses va_start, va_arg, va_end and so others? Cause it should be.