Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

C++ View Objects


Coding the View Class

#1: The view contains links to the underlying data, not copies of it

We want our view class to work with any type of data; we want to be able to use the same class to build a view of a vector of integers as we do to build a view of a vector of custom objects. Furthermore, we need the view to be container agnostic. What I mean by this is that there should be no restriction on the underlying data source. So we want the same view class to work for maps and vectors seamlessly and without change.

This means that we need to think a little more carefully about how we represent the underlying data because the underlying data can be of any type and can be stored in any type of container.

So let's think in general terms. We have a container of type C holding objects of type T and we want to create a view of type V in such a way that there are no extra copies of type C or T.

The first step is to consider what class of object the view will actually contain. If it is not allowed to contain copies of the underlying data then it cannot contain elements of class T. An obvious solution is instead of storing objects of type T in the view, we store objects of type P(T) where P(T) represents some kind of proxy of T.

If we stay within the conceptual realms of the Standard Library, an ideal P(T) is the iterator. An iterator is an indirection object or a proxy object used with all the Standard containers. So if we make our view class store iterators then we will be able to work with all the Standard containers seamlessly.

Therefore we have, our view V contains n elements of type P(T) where n is number of elements in the view and P(T) is an iterator to an object of type T.

Before we can express this in code, there is one more issue we need to deal with. A view is container agnostic but an iterator is not. For example, vector<int>::iterator is not of the same type as set<int>::iterator. So, although the view itself is not conceptually tied to the type of underlying container it does need to know the type so that it can determine what type the iterators P(T) are. We can let the view know all this by making the view a parameterised type and specifying the type of underlying container in a type parameter.

These intricate details turn out to be very easy to describe in code.

template<typename _CtnType>
class view{
protected:
	typedef _CtnType::const_iterator element_type;
	vector<element_type>_elements;
public:
	element_type operator[]( size_type x ){
		return _elements[x];
	}
}
	typedef typename vector< element_type >::iterator iterator;

Notice that I am using a vector object to store the iterators. This is just because vectors give the most flexibility and speed. This implementation detail will be hidden from the user of the view.

This definition gives us the first required behavior, that the view contains links to the underlying data not copies of it. However, if we left the view class like this we would not be making the usage of the class as nice as it could be.

We want to encapsulate the intricacies of the view from the code using the view but, at present, the fact that we are storing iterator objects is not hidden and means that code that accesses the view will constantly have to dereference the iterator object to get at the real data. This would give rise to the following style of code.

vector<wstring>ctn;
view<vector<wstring>>theView;

wstring v = *(theView[0]); //need to explicitly dereference the iterator

It is easy for us to dereference the iterator internally within the view meaning that the client code does not have to. The client code only needs to deal with the real data which makes the view much nicer to use.

However there are other considerations, which I haven't mentioned yet, which affect what type P(T) really should be. These considerations have to do with making the view as easy as possible for client code to use, both in the construction of the view and in accessing the view. Therefore, instead of P(T) being an iterator object let's define a separate proxy class called view_element which we will use instead.

The view_element class is just a lightweight wrapper of an iterator but it gives us the added capabilities of defining custom constructors and operators that allow us to finely control how our view class is used by client code and the encapsulations we make.

The view_element class needs to know the type of underlying container so that it knows the type of iterator that it stores. We'll pass this information in as a type parameter like before.

This gives the following definitions for the view_element and view classes.

template<typename _CtnType>
class view_element{
protected:
	typename _CtnType::const_iterator	_itr;
public:
	typedef typename _CtnType::value_type value_type;
	virtual const value_type& value() const{
		return *_itr;
	}
};

template<typename _CtnType>
class view{
protected:
	typedef view_element<_CtnType> element_type;
	typedef typename _CtnType::value_type value_type;
	vector<element_type> _elements;
public:
	value_type operator[]( size_type x ){
		return _elements[x].value();
	}
}

This is all the code we need to implement the first behavior, so let's continue onto the second.

#2: Only data that qualifies for the view can be accessed via the view

The purpose of this behavior is to make sure that the view object encapsulates only the subset of the data included in the view. Client code should not be able to use the view to access data outside of the view. It turns out that the code we wrote to implement the first behavior also provides us with the second behavior.

This is because the view class simply stores a vector of proxy objects (called view_elements) where each proxy represents only one element in the underlying data. Client code, however, receives the underlying data via the view_element proxy. The raw iterator to the underlying data is hidden within the view_element proxy and is not returned to the client. So the client has no way back to the underlying container and can only receive data via the view.

[Click image to view at full size]

Figure 3: Limiting the view's data access.

This would not have been the case if we had decided to store iterator objects directly in the view and not our view_element proxy. If the view object returned raw iterators to client code then client code could use these iterators to traverse to data outside of the view thus contradicting the purpose of the view in the first place.

An additional benefit is that any code can iterate over the contents of any view object without having to understand how the view was built or have a direct reference to or understanding of the underlying containers. This decoupling allows you to hide the internal details of the data storage and logic but still grant finely controlled access to the real data.

[Click image to view at full size]

Figure 4: Ease of external code access.

#3 The view can reference multiple underlying data containers

Views not need be limited to referring to only one container. Data from multiple containers can be combined to create a coherent and navigable view object that looks and acts no differently than any other view object. The consumer of the view does not know, or need to know, that the view object refers to multiple underlying containers.

[Click image to view at full size]

Figure 5: A view referencing multiple containers.

The view stores proxy objects each of which represents a particular piece of data in an underlying container. We have implemented this proxy using iterator objects. In order to access the real data via the iterator we simply have to dereference the iterator.

So our current view and view_element classes can already create multi-container views. All that is required is to add iterators that point to different containers into the same view object. It's as simple as that.

typedef vector< wstring > wstringVec;
wstringVec        ctn1;
wstringVec        ctn2;
view<wstringVec >     wstringVecView;

wstringVecView.add( ctn1.begin()+1 ); // Add the second element from ctn1
wstringVecView.add( ctn2.begin()+1 ); // Add the second element from ctn2

However, there is one caveat. Within a view object, all proxy objects have to be of the same type, P(T) where T is the type of the underlying containers. Therefore, all underlying containers have to be of type T.

So it is not possible, at least in this implementation, for a view< T > to point to containers that are not of type T.

This is because a view<T > is composed of view_element objects of type view_element< T >. So if we have two containers of type T0 and T1 and a view_element pointing to each then we have view_element< T0 > and and view_element< T1 >. If these view_elements are to be placed into a view object of type view< T > then they must be of type view_element< T >. Therefore, it follows that view_element< T0 > = view_element< T1 > = view_element<T > and this is only true if T0 = T1 = T.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.