excel in the craft and prevent rsi with stoyan damov, founder and co-owner, csgw ltd.csgw ltd....
TRANSCRIPT
Excel in the Craft and prevent RSI with
Stoyan Damov, founder and co-owner, CSGW Ltd.
[email protected]://www.boost.org/
The evolution of a C++ programmerReads a “Teach yourself C++ in 60 seconds” book
Decides he knows C++, adds C++ to his CV
Reads Bjarne Stroustrup's "The C++ Programming Language" vol. 1
Decides he now knows C++, adds “extensive C++ knowledge”
Reads Bjarne Stroustrup's "The C++ Programming Language" vol. 2
Writes “C++ expert” in his CV
Reads Scott Meyers's “Effective C++” and “Effective STL”
Replaces “expert” with “guru”
Reads Herb Sutter's “Exceptional C++”
Realizes “guru” is too much, replaces “guru” with “proficient in C++”
Reads Andrei Alexandrescu's “Modern C++ Design”
Depressed, replaces “proficient” with “experienced”
Reads dozens of C++ books, finds Boost, comp.lang.c++(.moderated)
Realizes he now knows how to use C++ (and brings back “proficient”)
Excel in the Craft and prevent RSI with
"...one of the most highly regarded and expertly designed C++ library projects in the world.“
Herb Sutter and Andrei Alexandrescu, C++ Coding Standards
#1. Pure laziness. “N more libs to learn, why?”I hope you’ll be convinced that learning and usingBoost will actually help you work less.
#2. “Haven’t checked, but my compiler probablycan’t compile it.”You’ll see it’s far from the truth even if you’re stuckwith MSVC 6.
#3. Sheer ignorance: “Who gives a @#$%?”Apparently doesn’t apply to all of you who attendthis lecture :)
Top 3 reasons Boost is ignored
You’ll type less code. I’ll show you.Makes C++ a more compelling language by overcoming language and library limitations.
Drives the language (10 libs in TR1, more will be in TR2) and compilers.Extends and works well with the Standard Library.
Portable across enough compilers and OS-es.
Free and non-proprietary.
Used by top software companies like Adobe, SAP, Real Networks, McAffee, (ahem) CSGW, etc.
Some reasons to use Boost
This lecture will introduce you toBoost and teach you how to:
Simplify object lifetime management and resource sharing with Boost.SmartPtr
Define function objects at call site and bind arguments to N-arity free functions, member functions and functors with Boost.Bind and Boost.Lambda
Define, store and implement callbacks with Boost.Function
Trigger and handle events (multicast callbacks) with Boost.Signals
This lecture will introduce you toBoost and teach you how to:
Store and retrieve values of any type with Boost.Any and Boost.Variant
Process text efficiently with Boost.Tokenizer, Boost.StringAlgo, Boost.Regex, Boost.Xpressive and Boost.Spirit
Write portable multithreaded code with Boost.Thread
Marshal and unmarshal objects in a versioned and platform-independant manner with Boost.Serialization
Use specialized containers when STL's are not (efficient) enough from Boost.MultiArray, Boost.MultiIndex and Boost.Intrusive
void leak_memory_recipe(){
T* p(new T());if (some_condition){
return;}delete p;
}
void shoot_your_self_in_the_foot(){
T* p(new T());free(p); // might launch Tetrisdelete p;delete p; // might format HDD
U* q(new U[42]);delete q; // might work in MSVC :)
void* p = malloc(sizeof(A));A* a = new(p) A();delete a; // might launch PacMan
}
Raw Pointers 1011
void contrived_example(){
// Windows Mobile -- thread stack is 8K:// allocate on heap to prevent stack overflow
T* p(new T());if (!foo(p->something())){
delete p; return; // slides suck}U* q(new U());if (!bar(“bar”, q)){
delete p; delete q; return;}p->do_something_useful_with(q);delete p;q->do_something_less_useful();delete q;
}
Raw Pointers
// today
bool bar(char const* p, U* u);
• Ugly• Error-prone
Can leak p & q in 3 different ways tomorrow. How?
// tomorrow
bool bar(Bar const& bar, U* u) throw (...);
struct Bar{ Bar(std::string const& s) throw (...);};
2
Dumb Pointerstemplate <typename T>struct dumb_ptr{
explicit dumb_ptr(T* p) : p_(p) {}~dumb_ptr() { delete p_; }
private: // non-copyable “stuff” omittedT* p_;
};
void hand_crafted_dumb_pointers(){
T* p(new T()); dumb_ptr<T> g1(p);if (!foo(p->something())){
return;}U* q(new U()); dumb_ptr<U> g2(q);if (!bar(“bar”, q)){
return;}p->do_something_useful_with(q);q->do_something_less_useful();
} // at end-of-scope, g1 and g2 delete pointees
3
Smart Pointers
void enter_auto_ptr(){
auto_ptr<T> p(new T());if (!foo(p->something())){
return;}auto_ptr<U> q(new U());if (!bar(“bar”, q)){
return;}p->do_something_useful();q->do_something_less_useful();
} // at end-of-scope, p and q delete pointees
// auto_ptr defined in <memory>
4
Smart Pointersstruct S{
// ...private:
auto_ptr<T> p_;};
// Q: who owns T*? (hint: elipsis are misleading)
struct S{
auto_ptr<T> good_bye(){
return p_;}T* farewell(){
return p_.release();}
private:auto_ptr<T> p_;
}; // A: whoever calls good_bye or farewell first
auto_ptr<T>
• sole ownership• transferable ownership• intention hard to guess
auto_ptr<T>
• sole ownership• transferable ownership• intention hard to guess
5
Smart Pointersvoid launch_tetris(){
auto_ptr<char> buffer(new char[42]);};
auto_ptr<T>
• sole ownership• transferable ownership• intention hard to guess• can’t be used with arrays
auto_ptr<T>
• sole ownership• transferable ownership• intention hard to guess• can’t be used with arrays
6
Smarter Pointersstruct S{private:
boost::scoped_ptr<T> p_;};
// It’s all in the name: S owns T*
scoped_ptr<T>
• sole ownership• non-transferable ownership• intention at first glance
scoped_ptr<T>
• sole ownership• non-transferable ownership• intention at first glance
template <class T> class scoped_ptr : noncopyable{
...// no release() function
}
7
void launches_tetris_as_well(){
scoped_ptr<char> buffer(new char[42]);};
Smarter Pointers
template <class T> class scoped_array : noncopyable{
...}
void foo(){
scoped_array<char> buffer(new char[42]);}
scoped_ptr<T>
• sole ownership• non-transferable ownership• intention at first glance• cannot be used with arrays but:
scoped_ptr<T>
• sole ownership• non-transferable ownership• intention at first glance• cannot be used with arrays but:
8
Raw Pointers (again)// every non-trivial multithreaded application// passes data between threads
void who_deletes_the_pointer(){
T* p(new T());launch_thread_and_pass_it(p);p->do_something_useful();
}
Кой, я ли не моа?
9
Reference-counted pointersstruct T : private noncopyable{
T() : refs_(1) {}void add_ref() { ++refs_; }void release(){
if (--refs_ == 0){
delete this;}
}void do_something_useful() { ... }
private:size_t refs_;
};
// continued on next slide…
10
void who_deletes_the_pointer(){
T* p(new T()); // refs_ == 1launch_thread_and_pass_it(p); // refs_ == 2p->do_something_useful();
/*a*/ p->release(); // refs_ == 1 or 0?}
void launch_thread_and_pass_it(T* p){
p->add_ref(); // refs_ == 2// actually launch thread and pass it p
}
void thread(T* p){
// refs_ == 2 or 1p->do_something_useless();
/*b*/ p->release(); // refs_ == 1 or 0?}
Pray these don’t throwOR
abuse try/catch constructs
Ugly, scattered, error-prone reference
counting
Reference-counted pointers11
launch_thread_and_pass_it(p); // refs_ == 2
void who_deletes_the_pointer() // thread 1 (T1)/*a*/ p->release();
void thread(T* p) // thread 2 (T2)/*b*/ p->release();
Multithreading 101: Race conditions1. refs_ = 2; (memory)2. Main thread (T1) reads the value of refs_ from memory into register 1 (r1) : 23. Secondary thread (T2) reads the value of refs_ from memory into register 2 (r2) : 24. T1 decrements the value of refs_ in r1: (r1 contents) - 1 = 15. T2 decrements the value of refs_ in r2: (r2 contents) - 1 = 16. T1 stores the value of r1 in memory : 17. T2 stores the value of r2 in memory : 18. refs_ = 1; (memory)
Pointer leaked.
Reference-counted pointers12
struct T : private noncopyable{
T() : refs_(1) {}void add_ref(){
impl_atomic_increment(refs_);}void release(){
if (impl_atomic_decrement(refs_) == 0){
delete this;}
}private:
implementation-specific refs_;};
Almost there: thread-safeReference-counted pointers
13
boost::shared_ptrtypedef boost::shared_ptr<T> Ptr;
void last_shared_ptr_deletes_the_pointer(){
Ptr p(new T());launch_thread_and_pass_it(p);p->do_something_useful();
}
void launch_thread_and_pass_it(Ptr p){
// launch thread and pass it p}
void thread(Ptr p){
p->do_something_useless();}
shared_ptr<T>
• shared ownership• thread-safe• cannot be used with arrays butshared_array<T> can.
shared_ptr<T>
• shared ownership• thread-safe• cannot be used with arrays butshared_array<T> can.
14
A few well-known conceptsCopyConstructible
struct T{ T(T const& other);};
Assignablestruct T{ T& operator=(T const& other);};
NonCopyablestruct S{private: T(T const& other); // not CopyConstructible T& operator=(T const& other); // not Assignable};
15
Why do I want to know these?
Standard Library Containers require thetemplate parameter used for the containers’element type to be both CopyConstructible
and Assignable.
auto_ptr<T>, scoped_ptr<T>, andscoped_array<T> do not meet theserequirements and so cannot be used inSTL containers.
16
STL containers with raw pointersstruct Big { ... };
struct T{
// ...~T();
private:// Big is expensive to copy, use pointerstypedef std::vector<Big*> BigItems;typedef BigItems::iterator iterator;BigItems items_;
};
T::~T // don’t forget to delete the pointers!{
iterator e(items_.end());for (iterator b(items_.begin()); b != e; ++b){
delete *b;}
}
17
STL containers with raw pointerstemplate <typename C>void clear_container(C& c){
std::for_each(c.begin(), c.end(), Deleter());}
struct Deleter{
template <typename T>void operator()(T const* p) const{
delete p;}
};
T::~T{
clear_container(items_);}
operator() is member template so Deleter
can deduce its argument
18
STL containers with shared_ptrstruct Big { ... };
struct T{private:
typedef boost::shared_ptr<Big> BigPtr;typedef std::vector<BigPtr> BigItems;BigItems items_;
};
No, really, that’s it :)
19
More STL containers withraw pointers
struct Big { ... /* defines operator< */ };
struct T{private:
struct BigPtrLess{
bool operator()(Big const* l,Big const* r) const
{return *l < *r;
}};typedef std::set<Big*, BigPtrLess> BigItems;BigItems items_;
};
20
More STL containers withshared_ptr (déjà vu)
struct Big { ... /* defines operator< */ };
struct T{private:
typedef boost::shared_ptr<Big> BigPtr;typedef std::set<BigPtr> BigItems;BigItems items_;
};
struct Big { ... };
struct T{private:
typedef boost::shared_ptr<Big> BigPtr;typedef std::vector<BigPtr> BigItems;BigItems items_;
};
shared_ptr defines relational operators so
pointers work as desired
21
shared_ptr in a nutshell
• shared ownership - underlying pointer deleted when the lastshared_ptr pointing to it is destroyed or reset
• copy-constructible and assignable, so can be used inSTL containers
• comparison operators are defined, so can be used inassociative containers
• part of TR1• eliminates explicit deletes in code• decreases the need for try/catch constructs• thread-safe
What’s not to like?!
22
struct Source;typedef shared_ptr<Source> SourcePtr;
struct Sink{ virtual void event_occurred() = 0;};typedef shared_ptr<Sink> SinkPtr;
struct Source{ virtual void set_observer(SinkPtr observer) = 0;};
struct SourceImpl : Source, private noncopyable{ void set_observer(SinkPtr observer);private: SinkPtr observer_;};
shared_ptr and cyclic references23
shared_ptr and cyclic referencesstruct SinkImpl : Sink, private noncopyable{ SinkImpl(SourcePtr source) : source_(source) {} void event_occurred() { ... }private: SourcePtr source_;};
void SourceImpl::set_observer(SinkPtr observer){ observer_ = observer; // Game Over: cyclic reference, both pointers leaked}
void SourceImpl::fire_event(){ if (observer_) observer_->event_occurred();}
SourcePtr source(new SourceImpl());SinkPtr sink(new SinkImpl(source));source->set_observer(sink);source->fire_event();
SinkImpl now has a strong reference to
Source
SourceImpl now has a strong reference
to Sink
24
Enter boost::weak_ptrstruct Source;typedef shared_ptr<Source> SourcePtr;
struct Sink{ virtual void event_occurred() = 0;};typedef shared_ptr<Sink> SinkPtr;
struct SourceImpl : Source{ void set_observer(SinkPtr observer);private:
weak_ptr<Sink> observer_;};
We still take a shared_ptr parameter
but store a weak_ptr
25
struct SinkImpl{ SinkImpl(SourcePtr source) : source_(source) {} void event_occurred() { ... }private: SourcePtr source_;};
void SourceImpl::set_observer(SinkPtr observer){ observer_ = observer;}
void SourceImpl::fire_event(){ if (SinkPtr p = observer_.lock()) { p->event_occurred(); }}
SourcePtr source(new SourceImpl()); ...
Enter weak_ptr (continued)
lock() returns shared_ptr<Sink>
shared_ptr can be used inboolean expressions, handy in
conditional assignments like this
26
struct SinkImpl : Sink, enable_shared_from_this<Sink>{ // SinkImpl(SourcePtr source) : source_(source) {}
void set_source(SourcePtr source) { source_ = source; source_->set_observer(shared_from_this()); } // ...};
boost::enable_shared_from_this
#include <boost/enable_shared_from_this.hpp>
27
string you_can_do_this_as_well(){ char* raw = reinterpret_cast<char*>(malloc(1024)); shared_array<char> mem(raw, free); call_an_api_function(mem.get()); return raw;}// free(mem.get()) called at end of scope
But wait, there’s more!!!
// RAII is about resources, not just memory
void process_file(char const* file_path){ shared_ptr<FILE> file(fopen(file_path, “r”), fclose); read_data_from_file(file.get());}// fclose(file.get()) called at end of scope
you can pass a custom deleter
28
Standard Library Bindersbind1stA helper template function that creates an adaptor to convert abinary function object into a unary function object by binding thefirst argument of the binary function to a specified value.
#include <boost/assign/std/vector.hpp> // operator+=()using namespace boost::assign;
typedef std::vector<int> Numbers;typedef Numbers::iterator iterator;Numbers nums;nums += 6, 9, 42, 54, 13; // yup, boost::assign rocks
iterator answer = find_if(nums.begin(),nums.end(), bind1st(equal_to<int>(), 42));
assert(answer != nums.end() && *answer == 42);
template <typename Operation, typename Type> binder1st<Operation> bind1st( Operation const& func, Type const& left);
template <typename Type>struct equal_to : binary_function<Type, Type, bool> { bool operator()( Type const& left, Type const& right) const;};
template <typename Type>struct equal_to : binary_function<Type, Type, bool> { bool operator()( Type const& left, Type const& right) const;};
template <typename InputIterator, typename Predicate>InputIterator find_if( InputIterator first, InputIterator last, Predicate pred); // Predicate: function object that defines// the condition to be satisfied by the element// being searched for.// A predicate takes single argument and returns// true or false.
29
Standard Library Bindersbind2ndA helper template function that creates an adaptor to convert abinary function object into a unary function object by binding thesecond argument of the binary function to a specified value.
// ...nums += 6, 9, 42, 54, 13; // yup, boost rocks
iterator it = find_if(nums.begin(),nums.end(), bind2nd(greater<int>(), 13));
assert(it != nums.end() && *it == 42);
template <typename Operation, typename Type> binder1st<Operation> bind2nd( Operation const& func, Type const& right);
30
Boost.Bindboost::bindGeneralization of the standard functions std::bind1st and std::bind2nd.Supports arbitrary function objects, functions, function pointers, andmember function pointers, and is able to bind any argument to a specificvalue or route input arguments into arbitrary positions.
#include <boost/bind.hpp>nums += 6, 9, 42, 54, 13;
iterator fourty_two = find_if(nums.begin(),nums.end(), bind(equal_to<int>(), 42, _1));
iterator it = find_if(nums.begin(),nums.end(), bind(greater<int>(), _1, 13)); // note: _1, not _2
a “this ain’t C++”WTF moment:
_1 is a placeholder
emulating bind1st
emulating bind2nd
Warning,magic ahead
31
Boost.BindWorks with functions and function pointers
int add(int x, int y) { return x + y; }int div(int x, int y) { return x / y; }
bind(add, _1, 7) // becomes unary function add(x, 7)// equivalent tobind2nd(ptr_fun(add), 7)// bind1st emulated as wellbind(add, 7, _1) // becomes unary function add(7, y)
bind(add, _1, 7)(3) // == add(3, 7) == 10
bind(add, 5, 7) // becomes nullary function add(5, 7)bind(add, 5, 7)() // == 12bind(add, 5, 7)(13, 42) // == 12 (extra args ignored)
// function pointersint (*pf)(int, int) = add;bind(pf, _2, _1)(2, 84) // pf(84, 2) == 42
btw, placeholders need not be in order
32
Boost.Bindbind3rd, bind4th..., bind<N>th
int f(int x, int y, int z);int g(int a, int b, int c, int d);
bind(f, _1, _2, _3)(5, 6, 7) // f(5, 6, 7)bind(f, _1, 42, _2)(5, 13) // f(5, 42, 13)bind(f, _2, _1, 42)(5, 13) // f(13, 5, 42)bind(f, _1, _1, _1)(42, 43) // f(42, 42, 42)
bind(g, 1, _1, 3, 4)(2) // g(1, 2, 3, 4)
33
Boost.BindWorks with function objects (functors)
struct Greeter{
void operator()(string const& name) const{
cout << “Hi “ << name << endl;}
};
Greeter g;bind<void>(g, _1)(“Boost”); // outputs “Hi Boost”bind<void>(ref(g), _1)(“Boost”); // dittobind<void>(Greeter(), _1)(“Boost”); // ditto
bind(less<int>(), _1, 42)
When the functor exposes a nested type named result_type (e.g. STL’s functors), the explicit return type can be omitted (but some compilers simply suck so it won’t work everywhere)
operator()’s return type must be explicitly specified
boost::ref in <boost/ref.hpp>avoids (expensive) copying of function objects
34
Boost.BindWorks with pointers to members
bind(&T::f, args)
struct T{
void f(int val);};
T t;shared_ptr<T> p(new T);int v = 42;
bind(&T::f, &t, _1)(v) // (&t)->f(v)bind(&T::f, ref(t), _1)(v) // t.f(v)
bind(&T::f, t, _1)(v) // (internal-copy-of-t).f(v)bind(&T::f, p, _1)(v) // (internal-copy-of-p)->f(v)
// the last two produce self-contained function objects // (a.k.a. closures)
35
Boost.BindOverloads operator! and relational operatorsstruct P // Participant – abbreviated to save space
{ explicit P(string const& name) : name_(name) {} P(P const& o) : name_(o.name_) {} P& operator=(P const& o) { return name_ = o.name_, *this; // one-liner to save space } string name_;};typedef vector<P> Participants;typedef Participants::iterator iterator;
Participants guys;guys += P("Stanimirov"), P("Nakov"), P("Nakov"), P("Penchev"); // ...
iterator i = adjacent_find(guys.begin(), guys.end(),!(bind(&P::name_, _1) != bind(&P::name_, _2)));
i = find_if(guys.begin(), guys.end(),bind(&P::name_, _1) == “Penchev”);
sort(guys.begin(), guys.end(), // Stanimirov, Penchev, Nakov, Nakovbind(&P::name_, _2) < bind(&P::name_, _1))
!(x != y) to demonstrate overloaded operator ! - equivalent to
bind(...name_, _1) == bind(...name_, _2)
36
Boost.Lambda# RubyLanguage = Struct.new(:name, :desc)# names = %w{ Ruby C# C++ }# languages = names.collect { | n | Language.new(n, 'cool') }
languages.collect { | lang | print lang.name, ' ' }# outputs: Ruby C# C++
// C# 2langs.ForEach(delegate(Language lang)
{ Console.Write("{0} ", lang.Name); });
// C# 3langs.ForEach(lang => Console.Write("{0} ", lang.Name));
// C++ before Boost.Lambdatransform(langs.begin(), langs.end(), ostream_iterator<string>(cout, " "), bind(&L::name_, _1));
// C++ after Boost.Lambdafor_each(langs.begin(), lang.end(),
cout << bind(&L::name_, _1) << ' ');
#include <boost/bind.hpp>using namespace boost;#include <boost/bind.hpp>using namespace boost;
#include <boost/lambda/lambda.hpp>#include <boost/lambda/bind.hpp>using namespace boost;using namespace boost::lambda;
#include <boost/lambda/lambda.hpp>#include <boost/lambda/bind.hpp>using namespace boost;using namespace boost::lambda;
37
Warning,magic ahead
Boost.Lambdastring name;for_each(
langs.begin(),lang.end(),
( var(name) = bind(&L::name_, _1), cout << var(name) << " is ", if_(var(name) == "C++") [cout << constant(“smart")] .else_ [cout << constant("dumb")], cout << constant(“\n"))
));
// Outputs:// Ruby is dumb// C# is dumb// C++ is smart
brackets are necessary, for_each accepts 3 arguments
Lambda expressions for control structures (flow control) Boost.Lambda supports: if-then[-else], while, do-while, for, switch; also try-catch, construction, cast, etc. expressions
delays evaluation of constants and variables
commas chain lambda expressions
Yes, thisis C++
38
Boost.FunctionStores function pointers and function objects for subsequent invocation.
Anywhere where a function pointer would be used to defer a call or make a callback, Boost.Function can be used instead to allow the user greater flexibility in the implementation of the target.
Targets can be any 'compatible' function object (or function pointer), meaning that the arguments to the interface designated by Boost.Function can be converted to the arguments of the target function object
Preferred syntax Portable syntax
function<void()> f; function0<void> f;function<bool(int n)> f; function1<bool, int> f;
digit after “function” definesfunction arity (number of args)
39
Boost.Functionfunction<bool(int)> f;
Free Functionsbool foo(int bar);f = foo; // f = &foo for MSVC 6 devsbool ret = f(42); // same as foo(42);
Functorsstruct T{
bool operator()(int bar);};f = T();bool ret = f(42); // same as T::operator()(42);
// if T is expensive to copyT t;f = ref(t); // boost::ref
40
Boost.Functionfunction<bool(T*, int)> f;
Member functionsstruct T{
bool foo(int bar);};
f = &T::foo;
T t;
f(&t, 42);
41
Boost.FunctionUsage
template <typename T>struct ThreadSafeContainer{
vector<T> items_;mutex mx_; // perhaps boost::mutex
};
// how to iterate its items in a thread-safe manner?
struct ThreadSafeContainer{
// 1) return a copyvector<T> return_copy(){
mutex::scoped_lock lock(mx_);return vector<T>(items_.begin(), items_.end());
}// expensive :(
};
42
Boost.FunctionUsage
// how to iterate its items in a thread-safe manner?
struct ThreadSafeContainer{
// 2) expose the mutexmutex& get_mx(){
return mx_;}// error-prone, exposes implementation details
};
43
Boost.FunctionUsage
// how to iterate its items in a thread-safe manner?
struct ThreadSafeContainer{ // 3) enumeration while locked void enumerate(function<void(T const& value)> enumerator) { mutex::scoped_lock lock(mx_); for_each(items_.begin(), items_.end(), enumerator); }};
ThreadSafeContainer<int> cont;// ...fill it in
// iterate in a thread-safe mannercont.enumerate(cout << _1 << constant("\n"));
44
Boost.Signals
Managed signals and slots system
Signal: callback with multiple targets (multicast callback)a.k.a. publishera.k.a. event
Signals are connected to a set of slots (callback receivers)a.k.a. subscribersa.k.a. event targets
Makes Java’s listeners and .NET’s events laughable.
45
Boost.SignalsPreferred syntax Portable syntax
signal<void()> event; signal0<void> event;signal<bool(int n)> sig; signal1<bool, int> sig;
event(); // fire event - no slots connected, nothing happens
// single slotvoid foo() { cout << “foo” << endl; }event.connect(&foo);event(); // outputs “foo”, new line
// multiple slots (foo and bar) connectedvoid bar() { cout << “bar” << endl; }event.connect(&foo);event.connect(&bar);event(); // outputs “foo”, new line, “bar”, new line
digit after “signal”declares arity
(number of args)
can have return value
46
Boost.SignalsOrdering slotssignal<void()> sig;
void foo() { cout << “foo”; }void bar() { cout << “bar”; }void eol() { cout << endl; }
sig.connect(&eol);sig.connect(1, &bar);sig.connect(0, &foo);sig(); // outputs “foobar”, end-of-line
Passing values to slotssignal<void(int, int)> sig;
void sum(int x, int y) { cout << x << "+" << y << "=" << x + y; }void mul(int x, int y) { cout << x << "*" << y << "=" << x * y; }
sig.connect(&sum);sig.connect(&mul);sig(6, 7); // outputs: “6 + 7 = 13” and “6 * 7” = 42
slot order is defined by an optional 1st
parameter
non-grouped slots called last
47
Boost.SignalsObtaining values from slotssignal<int(int, int)> calc;
int sum(int x, int y) { return x + y; }int mul(int x, int y) { return x * y; }
sig.connect(&sum);sig.connect(&mul);
int meaningless_result = sig(6, 7);
// ^^^ returns last called slot’s result
48
Boost.SignalsObtaining values from slotstemplate <typename T> struct maximum{ typedef T result_type;
template <typename Iter> T operator()(Iter first, Iter last) const { // No slots to call -> return default-constructed value if (first == last) { return T(); }
T max_value = *first++; while (first != last) { if (max_value < *first) { max_value = *first; } ++first; } return max_value; }};
signal<int(int, int), maximum<int> > calc;int max = sig(6, 7); // max(13, 42) == 42
maximum is a combiner
combiner is a mechanism that can take the results of calling slots and coalesces them into a single result to be returned to the caller
allows for type deduction of signal’s return value
The input iterators passed to the combiner transform dereference operations into slot calls. This pull-based interface makes writing combiners a lot more easier.
49
Boost.SignalsObtaining values from slotstemplate <typename C> struct aggregator{ typedef C result_type;
template <typename Iter> T operator()(Iter first, Iter last) const { return C(first, last); }};
signal<int(int, int), aggregator<vector<int> > > calc;vector<int> all_results(sig(6, 7));
50