Overview
This is a series of examples that demonstrate how to get the sum of a vector of primitives, then objects, and finally shared pointers to objects. It starts with one typical way of accomplishing the task. It then goes into several more examples. The final example will probably seem like quite a huge leap over the next to last example. At that point, there will be another series of examples that builds up to that final example.
Requirements
These examples use The Eclipse CDT, mingw, CppUTest 2.1 and gcc 4.4. If you need to setup the entire environment, here are those steps:
- Getting The CDT Running
- Getting CppUTest Compiled Using CDT Tool Set
- Getting CppUTest Running
- Getting and Building Boost in minggw
- Using Boost With mingw And Eclipse
- Configuring gcc to use C++0x in Eclipse Cdt
Summing vector<int>
SummingVectorOfInts.cpp
This first example simply demonstrates the mechanics of manually summing up the values of integers. The final result looks quite a bit different. Before we even get there, however, let’s move on to vectors with objects rather than primitives.
Smming Vector of Objects
SummingVectorExample1.cpp
This code seems barely different enough to warrant a whole example. Even so, I’m trying to show the background material you’ll need to know to understand the final version. There are several steps along the way.
Using accumulate with vector of ints
The standard library includes std::accumulate that accomplishes what we’ve already shown with std::for_each. Back to primitives before moving to the object:
AccumulateVectorOfInts.cpp
How does this work? Accumulate iterates over each element of the collection. On the first iteration it adds the “seed” value, which is 0, to the first value in the vector, which is 1. 0 + 1 -> 1, which becomes the new seed value. On the second loop, the seed value is 1, and the second value is 5. 5 + 1 -> 6, which becomes the new seed value. On the final time through the loop, the value 6 is added to the final value in the collection, 7. 6 + 7 -> 13.
In fact, the underlying algorithm uses + to add the values. The seed value is an int. The vector contains ints. The built-in + operator takes two ints and returns an int, so everything works as expected.
accumulate vector of objects with custom operator+
Now that you’ve seen the basic algorithm, let’s move on to a vector of objects:
SummingVectorsExample2.cpp
The accumulate algorithm needs a + operator that can take the seed value (an int in this case), and a value from the vector (a Value_2 object), add the results and return a value eequivalent to the seed type. That’s what this function does:
This certainly works, but can we do any better than having to write a function? This seems like a common problem.
accumulate vectors of objects with conversion operator
Another option is to provide a conversion operator. Since we are doing something simple, adding ints, this happens to work:
SummingVectorsExample3.cpp
This works, as mentioned, because all the code is trying to sum on ints stored in an object. However, a conversion operator only works if there’s only one way to get an appropriate value. If there were two int values, which would the conversion operator return? There are ways to make that work, but there’s an even better way.
Using accumulate and calling a member
This is going to be a bit of a leap. After this, there’s a series of much smaller steps showing how to get to the final version. SummingVectorsExample4.cpp
What?! This is the large leap mentioned above. In a nutshell, the code passes in a function object, std::plus
That complex expresion is a composition of function objects.
Building up to the Complex Composition & Beyond
The final version is a huge jump from the immediately proceeding version. How can we go from one to the next?
This is Where C++0x comes into play
These next examples use type inference, one of the features in C++0x. If you followed the steps mentioned above, you’ll be able to take this code as is and get it to compile. (All of the examples here come from compiling code with all of the tests passing.)
The Infrastructure
First, the top of the source file:
Notice that the Value class has both getValue() and operator int(). The conversion operator (operator int()) is used in one of the examples but not the final version.)
Calling the functor Directly
This first test demonstrates simply calling add with two arguments:
This works because std::plus
implements operator()
. This is significant because when an instance of std::plus
is passed into a template method, the template method can execute it as if it were a function. Consider the following:
Initially, theres a definition for foo
, which simply adds two values. bar
calls foo
directly.
The function baz
creates a pointer to a function, called f_pointer
, and initializes it with foo
. It then calls foo
through the pointer to a function.
The function muchBetter
demonstrates the same thing using a functor,std::plus
, that has an operator()
as part of its definition.
Why all of this background? Well what if I have the following template method:
This template method,execute
, imposes one requirement on its type F; Instances of f must respond to operator()
. A function like foo
does, as demonstrated in the first line of callBoth
. The instance of the functor std::plus<int>
also responds to operator()
. This code compiles and defines two implementations of the execute
function.
Binding Both Parameters
Notice how the template method execute
hard-codes the values of the parameters called on either foo
or std::plus<int>
? This is called Currying. This code demonstrates the same thing using boost::bind
:
The call to boost::bind
creates an instance of a functor, which is stored in a type-inferred variable called functor (this name is meant to be self-explaining, but is otherwise not significant). This instance internally stores three things:
- A copy of add
- A copy of 10
- A copy of the result of calling v.getValue(), or 42.
Since the two parameters required by std::plus have been stored internally, executing functor
requires no parameters.
Binding the second parameter
What if we want to bind the second parameter, but allow the first one to be provided by the caller:
This accomplishes that. Note that we could use std::bind1st. I prefer boost::bind because it’s more general and works well with other parts of the boost library. In this case, the object returned from bind:
- Stores add
- Stores _1, which really means use the first parameter passed into the call of operator()
- Stores the result of calling v.getValue(), which is 42.
Notice the call to functor(10)
. 10 is the first, and only, parameter. The expression _1 from before will bind to that value.
Binding no parameters
What if you want to just simply wrap the std::plus<int>
instance:
This example does that. This time, the return from bind
:
- Stores add
- Stores _1, which will bind to the first parameter passed into
operator()
- Stores _2, which will bind to the second parameter passed into
operator()
Notice the call to functor(10, 42)
. 10 is the first parater, which binds to _1. 42 is the second parameter, which binds to _2.
Binding the second parameter to a method call
Now things get more complex, and realistic. Rather than calling v.getValue() directly, we’ll instead provide a binding to a call to v.getValue(). The call to v.getValue() was happening before
the creation of the functor, in fact, even before the call to boost::bind
. Now it will happen after
the creation of the functor. In fact, it will be called during
the execution of operator():
As before, the functor returned from bind
:
- Stores add
- Stores _1, which is a reference the the first parameter passed into operator()
Now, however, it also stores the result of
bind(&Value::getValue, _2)
, which is functor object that: - Stores a pointer to a member function, Value::getValue
- Refers to _2, which is the second parameter passed in to the outer-most call of
operator()
.
This is a game-changer. Notice that rather than calling functor(10, v.getValue())
, this is instead calling functor(10, v)
. When the operator()
method executes, it binds 10 to _1. It binds the result of calling operator()
on bind(&Value::getValue, _2)
to _2. So here’s what happens (not necessarily in this order):
- The
operator()
function first associates 10 with _1 (in reality, _1 is itself a function objects that accesses the first parameter passed into theoperator()
method). - The
operator()
function callsoperator()
on the functor returned from the inner-most call tobind
. This calls the methodValue::getValue
on v, which returns 42 from v. This value, 42, is associated with the final version of _2. - 10 and 42 are then passed into
std::plus<int>::operator()
, which returns 52.
How you’re probably write this in practice
This is how you’d probably write this in practice
Return to accumulate
With that background, we can now return to accumulate
on an array of Value objects:
This invocation of std::accumulate
makes use of the conversion operator Value::operator int()
. As mentioned above, this is not what we want to do. We want to call a method on each instance of value in the array, choses at the time the call to std::accumulate
occurs. However, you already know how to do this from the immediately proceeding example.
Back to the final solution
Here’s the merging of these two results together:
In practice, using a temporary is not how you’d probably write this code:
Working with vectors of shared_ptr
Let’s look at one final example. Imagine you want to have a vector of dynamically-allocated objects. Instead of storing raw pointers, you might think to use some kind of smart pointer, such as std::auto_ptr
. However, you cannot put std::auto_pointer
into standard collections. So instead, you decide to use boost::shared_ptr
. Here’s a final example that does all of that:
This is a place where boost::bind
shines over std::bind1st
(or std::bind2nd
).
It’s smart enough to just work in this situation. This example is packed with a lot of details:
- Using typedefs to give (hopefully) better names to types.
- Dynamic allocation without an apparent deallocation by using
boost::shared_ptr
- Function currying and nesting
- An actual, executing and passing unit test.
The Whole File
Each of these examples are pulled from one larger, compiling C++ source file. Here’s the whole file:
PlusAndBindTest.cpp
Comments