Blog Archive for / 2016 /
Christmas Sale
Tuesday, 06 December 2016
It's coming up to Christmas, and we're getting in the festive mood, so we're running a sale: Just::Thread Pro will be available for 50% off the normal price until the end of December 2016.
Just::Thread Pro provides high level facilities
on top of the C++ Standard Thread Library: an
Actor framework for easier
concurrency, along with concurrent data structures: a
thread-safe queue,
and
concurrent hash map,
and a wrapper for ensuring
synchronized access to single objects,
as well as an implementation of the Concurrency TS, including
atomic_shared_ptr
and
continuations
All licences include a free upgrade to point releases, so if you purchase now you'll get a free upgrade to all 2.x releases.
Posted by Anthony Williams
[/ news /] permanent link
Tags: sale
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Reclaiming Data Structures with Cycles
Friday, 14 October 2016
In my CppCon 2016 Trip Report I mentioned Herb Sutter's Plenary, and his deferred reclamation library. The problem to be solved is that some data structures cannot be represented as a DAG, where each node in the structure has a clear owner; these data structures are general graphs, where any node may hold a pointer to any other node, and thus you cannot say that a node is no longer needed just because one of the pointers to it has been removed. Likewise, it is possible that there may be a whole segment of the data structure which is no longer accessible from outside, and the only remaining pointers are those internal to the data structure. This whole section should therefore be destroyed, otherwise we have a leak.
In the data structure shown below, the left-hand set of nodes have 3 external pointers keeping them alive, but the right-hand set are not accessible from outside, and so should be destroyed.
Deferred Reclamation
Herb's solution is to use a special pointer type deferred_ptr<T>
, and a
special heap and allocator to create the nodes. You can then explicitly check
for inaccessible nodes by calling collect()
on the heap object, or rely on all
the nodes being destroyed when the heap itself is destroyed. For the details,
see Herb's description.
One thing that stood out for me was the idea that destruction was deferred — nodes are not reclaimed immediately, but at some later time. Also, the use of a custom allocator seemed unusual. I wondered if there was an alternative way of handling things.
Internal pointers vs Root pointers
I've been contemplating the issue for the last couple of weeks, and come up with an alternative scheme that destroys unreachable objects as soon as they become unreachable, even if those unreachable objects hold pointers to each other. Rather than using a custom heap and allocator, it uses a custom base class, and distinct pointer types for internal pointers and root pointers.
My library is also still experimental, but the source code is freely available under the BSD license.
The library provides two smart pointer class templates: root_ptr<T>
and
internal_ptr<T>
. root_ptr<T>
is directly equivalent to
std::shared_ptr<T>
: it is a reference-counted smart pointer. For many uses,
you could use root_ptr<T>
as a direct replacement for std::shared_ptr<T>
and your code will have identical behaviour. root_ptr<T>
is intended to
represent an external owner for your data structure. For a tree it could
hold the pointer to the root node. For a general graph it could be used to hold
each of the external nodes of the graph.
The difference comes with internal_ptr<T>
. This holds a pointer to another
object within the data structure. It is an internal pointer to another
part of the same larger data structure. It is also reference counted, so if
there are no root_ptr<T>
or internal_ptr<T>
objects pointing to a given
object then it is immediately destroyed, but even one internal_ptr<T>
can be
enough to keep an object alive as part of a larger data structure.
The "magic" is that if an object is only pointed to by internal_ptr<T>
pointers, then it is only kept alive as long as the whole data structure has an
accessible root in the form of an root_ptr<T>
or an object with an
internal_ptr<T>
that is not pointed to by either an root_ptr<T>
or an
internal_ptr<T>
.
This is made possible by the internal nodes deriving from internal_base
, much
like std::enable_shared_from_this<T>
enables additional functionality when
using std::shared_ptr<T>
. This base class is then passed to the
internal_ptr<T>
constructor, to identify which object the internal_ptr<T>
belongs to.
For example, a singly-linked list could be implemented like so:
class List{
struct Node: jss::internal_base{
jss::internal_ptr<Node> next;
data_type data;
Node(data_type data_):next(this),data(data_){}
};
jss::root_ptr<Node> head;
public:
void push_front(data_type new_data){
auto new_node=jss::make_owner<Node>(new_data);
new_node->next=head;
head=new_node;
}
data_type pop_front(){
auto old_head=head;
if(!old_head)
throw std::runtime_error("Empty list");
head=old_head->next;
return old_head->data;
}
void clear(){
head.reset();
}
};
This actually has an advantage over using std::shared_ptr<Node>
for the links
in the list, due to another feature of the library. When a group of interlinked
nodes becomes unreachable, then firstly each node is marked as unreachable, thus
making any internal_ptr<T>
s that point to them become equal to nullptr
. Then
all the unreachable nodes are destroyed in turn. All this is done with iteration
rather than recursion, and thus avoids the deep recursive destructor chaining
that can occur when using std::shared_ptr<T>
. This is similar to the behaviour
of Herb's deferred_ptr<T>
during a collect()
call on the deferred_heap
.
local_ptr<T>
completes the set: you can use a local_ptr<T>
when traversing a
data structure that uses internal_ptr<T>
. local_ptr<T>
does not hold a
reference, and is not in any way involved in the lifetime tracking of the
nodes. It is intended to be used when you need to keep a local pointer to a
node, but you're not updating the data structure, and don't need that pointer to
keep the node alive. e.g.
class List{
// as above
public:
void for_each(std::function<void(data_type&)> f){
jss::local_ptr<Node> node=head;
while(node){
f(node->data);
node=node->next;
}
}
}
Warning: root_ptr<T>
and internal_ptr<T>
are not safe for use if
multiple threads may be accessing any of the nodes in the data structure while
any thread is modifying any part of it. The data structure as a whole
must be protected with external synchronization in a multi-threaded context.
How it works
The key to this system is twofold. Firstly the nodes in the data structure
derive from internal_base
, which allows the library to store a back-pointer to
the smart pointer control block in the node itself, as long as the head of the
list of internal_ptr<T>
s that belong to that node. Secondly, the control
blocks each hold a list of back-pointers to the control blocks of the objects
that point to them via internal_ptr<T>
. When a reference to a node is dropped
(either from an root_ptr<T>
or an internal_ptr<T>
), if that node has no
remaining root_ptr<T>
s that point to it, the back-pointers are checked. The
chain of back-pointers is followed until either a node is found that has an
root_ptr<T>
that points to it, or a node is found that does not have a
control block (e.g. because it is allocated on the stack, or owned by
std::shared_ptr<T>
). If either is found, then the data structure is
reachable, and thus kept alive. If neither is found once all the
back-pointers have been followed, then the set of nodes that were checked is
unreachable, and thus can be destroyed. Each of the unreachable nodes is then
marked as such, which causes internal_ptr<T>
s that refer to them to become
nullptr
, and thus prevents resurrection of the nodes. Finally, the unreachable
nodes are all destroyed in an unspecified order. The scan and destroy is done
with iteration rather than recursion to avoid the potential for deep recursive
nesting on large interconnected graphs of nodes.
The downside is that the time taken to drop a reference to a node is dependent on the number of nodes in the data structure, in particular the number of nodes that have to be examined in order to find an owned node.
Note: only dropping a reference to a node (destroying a pointer, or reassigning a pointer) incurs this cost. Constructing the data structure is still relatively low overhead.
Feedback
Please let me know if you have any comments on my internal pointer library, especially if you have either used it successfully, or have tried to use it and found it doesn't work for you.
Posted by Anthony Williams
[/ cplusplus /] permanent link
Tags: cplusplus, reference counting, garbage collection
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
CppCon 2016 Trip Report
Wednesday, 12 October 2016
So, CppCon 2016 has finished, and I'm back home and all caught up with "normal" life again. I thought it was about time I wrote up my trip report before it was too late.
Pre-conference Workshop
For me, the conference started on Saturday 17th September, as I was running a two-day workshop on Concurrent Thinking. This was well-attended, and I had some great conversations with people during the breaks and at the end of each day.
The main conference
The main conference started on Monday morning, with a keynote from Bjarne Stroustrup on the evolution of C++. He walked us through how far C++ has come since its humble beginnings, and what he hopes to see in the future — essentially all those things he hoped to see in C++17 that didn't make it, plus a couple of extras.
Over the course of the rest of the week there were over 100 sessions across a wide variety of C++-related topics. It was often hard to choose which session to go and see, but since everything was recorded, it was possible to catch up afterwards by watching the CppCon Youtube Channel.
Highlights for me included:
-
Kenny Kerr and James McNellis on Embracing Standard C++ for the Windows Runtime (Video). Kenny and James talked about the new standard C++ projection for the Windows Runtime, which provides essentially a set of smart pointer wrappers for all the Windows Runtime types to hide the messy COM-style boilerplate that would otherwise be required. They compared a simple .NET app, the pages of boilerplate code required today in C++ to do the same, and then showed how it is again simple with the new library. I look forward to being able to use it for writing Windows-based applications.
-
Hartmut Kaiser on Parallelism in Modern C++ (Video). Hartmut talked about the new parallel STL, how futures and asynchronous operations work together to take advantage of parallel hardware, and issues like data placement, vectorization, and the potential for moving work to GPUs.
- Michael Spencer on My Little Optimizer: Undefined Behavior is Magic (Video). Michael showed how the presence of undefined behaviour can drasticly change the output of code generated by an optimizing compiler, and can actually let it generate better code. This was very interesting to see. We all know that we need to avoid undefined behaviour, but it's enlightening to see how the existence of undefined behaviour at all can improve optimization.
Every presentation I watched was great, but these stood out. I still have a long list of sessions I'm going to watch on video; there is just so much to take in.
The plenary was by Herb Sutter, who talked about "Leak Freedom by default". The first half of the
talk was a summary of what we have in the standard library today — std::unique_ptr<T>
and
std::shared_ptr<T>
do most of the heavy lifting. He showed a poster "to stick on your colleague's
wall" showing which to use when. The remainder of the talk was discussion around the remaining
cases, notably those data structures with cycles, which are not well-supported by today's
standard library. In particular, Herb introduced his "experimental" deferred-reclamation (i.e. Garbage
Collection) library, which uses a custom heap and
deferred_ptr<T>
to allow you to detect and destroy unreachable objects. This got me thinking if
there was another way to do it, which will be the subject of a later blog post.
The people
By far the best part of the conference is the people. I had many in-depth discussions with people that would be hard to have via email. It was great to meet people face to face; some I was meeting for the first time, and others who I haven't met in person for years.
While you can watch the videos and read the slides without attending, there is no substitute for the in-person interactions.
My sessions
As well as the workshop, I presented a talk on The Continuing Future of C++ Concurrency, which was on Tuesday afternoon, and then I was on the panel for the final session of the conference: Implementing the C++ Standard Library on Friday afternoon.
As for the other sessions, videos are available on the CppCon Youtube channel:
Plus, you can also download my slides for The Continuing Future of C++ Concurrency.
Posted by Anthony Williams
[/ news /] permanent link
Tags: conferences, cppcon, C++, concurrency, workshop, slides
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
CppCon 2016 Workshop and Talk
Monday, 08 August 2016
I will be running my new 2-day workshop on Concurrent Thinking, as well as doing a session on The Continuing Future of Concurrency in C++ at CppCon 2016 in September.
I rarely leave the UK, and haven't been to the USA for 20 years, so this should be exciting. The CppCon program looks jam-packed with interesting talks, so it'll be hard to choose which to attend, and I'm looking forward to talking face-to-face with people I've only previously conversed with via email.
My workshop is on 17th-18th September, and the main conference is running 19th-23rd. If you haven't got your ticket already, head on over to CppCon Registration to get yours now.
Hope to see you there!
Posted by Anthony Williams
[/ news /] permanent link
Tags: conferences, cppcon, C++, concurrency, workshop
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
just::thread Pro adds gcc 6 support
Thursday, 21 July 2016
I am pleased to announce that just::thread
Pro
now supports gcc 5 and 6 on Ubuntu Linux.
The code has also been refactored, so with Microsoft Visual Studio 2015, g++ 5
or g++ 6 you can use the just::thread
Pro enhancements on top of the
platform-supplied version of the C++14 thread library. For older compilers, and
for MacOSX, the just::thread
compatibility library is still required.
Dubbed just::thread
Pro Standalone, the new build features all the same
facilities as the previous release:
- A multiple-producer single-consumer FIFO queue, ideal for sending messages to a particular thread
- A
synchronized_value
class template for synchronizing access to a single object - A thread-safe hash map
- An Actor framework for simplified design of multi-threaded applications
- A variadic
jss::lock_guard
class template to allow acquiring multiple locks at once, like the new C++17std::lock_guard
. - New facilities from the
Concurrency TS:
- A lock-free implementation of
atomic_shared_ptr
andatomic_weak_ptr
— see Anthony's earlier blog post onatomic_shared_ptr
- Latches — signal waiting threads once a specified number of count-down events have occurred.
- Barriers — block a group of threads until they are all ready to proceed.
future::then()
— schedule a task to run when a future becomes ready.when_any()
— create a future that is ready when any of a set of futures is ready.when_all()
— create a future that is ready when all of a set of futures are ready.
- A lock-free implementation of
Get your copy of just::thread
Pro
Purchase your copy and get started now.
As usual, all customers with V2.x licenses of just::thread
Pro will get a free
upgrade to the new just::thread
Pro Standalone edition.
Posted by Anthony Williams
[/ news /] permanent link
Tags: multithreading, concurrency, C++11
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
NDC Oslo 2016 Presentation Slides
Friday, 24 June 2016
NDC Oslo 2016 was 6th-10th June 2016.
I thoroughly enjoyed the conference. There were 9 tracks to choose from, so there was a wide range of topics covered. Though I mostly attended talks from the C++ track, I did branch out on a couple of occasions, espcially for the fun sessions, such as "Have I got NDC Oslo for you".
I ran my new 2-day workshop on Concurrent Thinking, which went well, with 23 students.
I also did two presentations:
- The Continuing Future of C++ Concurrency
- Safety: off --- How not to shoot yourself in the foot with C++ atomics
Slides for the presentations are available here:
- Slides for The Continuing Future of C++ Concurrency
- Slides for Safety: off --- How not to shoot yourself in the foot with C++ atomics
The videos are also being published on Vimeo:
- Video for The Continuing Future of C++ Concurrency
- Video for Safety: off --- How not to shoot yourself in the foot with C++ atomics
Posted by Anthony Williams
[/ news /] permanent link
Tags: conferences, ndc, C++, concurrency
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
NDC Oslo 2016
Monday, 30 May 2016
It's a week to go before NDC Oslo 2016. The conference starts on Monday 6th June with 2 days of workshops and runs through to Friday 10th June.
I will be running my new 2-day workshop on Concurrent Thinking, as well as doing two presentations:
- The Continuing Future of C++ Concurrency
- Safety: off --- How not to shoot yourself in the foot with C++ atomics
With 163 speakers including Andrei Alexandrescu and Joe Armstrong, and 5 tracks it looks to be an exciting conference.
Hope to see you there!
Posted by Anthony Williams
[/ news /] permanent link
Tags: conferences, ndc, C++, concurrency
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Slides for my ACCU 2016 presentation
Friday, 29 April 2016
Now that the ACCU 2016 conference is over, and we've all had a chance to recover, I figured it was time to post the slides from my presentation.
My session was titled "Concurrent Thinking". It was well-attended, with people standing round the edges due to the lack of seats, and I had people say afterwards that they liked it, which is always nice. I hope everyone learned something useful. Here's the abstract:
One of the most difficult issues around designing software with multiple threads of execution is synchronizing data.
Whether you use actors, active objects, futures and continuations or mutable shared state, every non-trivial system with multiple threads needs to transfer data between them. This means thinking about which data needs to be processed by which thread, and ensuring that the right data gets to the right threads in the right order. It also means thinking about API design to avoid race conditions.
In this presentation I’ll describe techniques we can use when doing this "thinking", as well as the tools we have available to help us describe our requirements and enforce them in code.
All examples will use C++, but the thought processes are widely applicable.
The slides are available here.
Posted by Anthony Williams
[/ news /] permanent link
Tags: C++, lockfree, atomic, accu
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
ACCU 2016 - Concurrent Thinking
Friday, 15 April 2016
The ACCU 2016 conference is next week. The conference starts on Tuesday 19th April with the tutorial and workshop day and runs through to Saturday 23rd April.
I will be talking about "Concurrent Thinking" on the Saturday at 11:30am. This 90 minute session is a taster of my new 2-day workshop, which I will be running at NDC Oslo in June and CppCon in September.
Here's the abstract:
One of the most difficult issues around designing software with multiple threads of execution is synchronizing data.
Whether you use actors, active objects, futures and continuations or mutable shared state, every non-trivial system with multiple threads needs to transfer data between them. This means thinking about which data needs to be processed by which thread, and ensuring that the right data gets to the right threads in the right order. It also means thinking about API design to avoid race conditions.
In this presentation I’ll describe techniques we can use when doing this "thinking", as well as the tools we have available to help us describe our requirements and enforce them in code.
All examples will use C++, but the thought processes are widely applicable.
Hope to see you there!
Posted by Anthony Williams
[/ news /] permanent link
Tags: conferences, accu, C++, concurrency
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Core C++ - lvalues and rvalues
Saturday, 27 February 2016
One of the most misunderstood aspect of C++ is the use of the terms lvalue and rvalue, and what they mean for how code is interpreted. Though lvalue and rvalue are inherited from C, with C++11, this taxonomy was extended and clarified, and 3 more terms were added: glvalue, xvalue and prvalue. In this article I'm going to examine these terms, and explain what they mean in practice.
Before we discuss the importance of whether something is an lvalue or rvalue, let's take a look at what makes an expression have each characteristic.
The Taxonomy
The result of every C++ expression is either an lvalue, or an rvalue. These terms come from C, but the C++ definitions have evolved quite a lot since then, due to the greater expressiveness of the C++ language.
rvalues can be split into two subcategories: xvalues, and prvalues, depending on the details of the expression. These subcategories have slightly different properties, described below.
One of the differences is that xvalues can sometimes be treated the same as lvalues. To cover those cases we have the term glvalue — if something applies to both lvalues and xvalues then it is described as applying to glvalues.
Now for the definitions.
glvalues
A glvalue is a Generalized lvalue. It is used to refer to something that could be either an lvalue or an xvalue.
rvalues
The term rvalue is inherited from C, where rvalues are things that can be on the Right side of an assignment. The term rvalue can refer to things that are either xvalues or prvalues.
lvalues
The term lvalue is inherited from C, where lvalues are things that can be on the Left side of an assignment.
The simplest form of lvalue expression is the name of a variable. Given a variable declaration:
A v1;
The expression v1
is an lvalue of type A
.
Any expression that results in an lvalue reference (declared with &
) is also
an lvalue, so the result of dereferencing a pointer, or calling a function
that returns an lvalue reference is also an lvalue. Given the following
declarations:
A* p1;
A& r1=v1;
A& f1();
The expression *p1
is an lvalue of type A
, as is the expression f1()
and
the expression r1
.
Accessing a member of an object where the object expression is an lvalue is also an lvalue. Thus, accessing members of variables and members of objects accessed through pointers or references yields lvalues. Given
struct B{
A a;
A b;
};
B& f2();
B* p2;
B v2;
then f2().a
, p2->b
and v2.a
are all lvalues of type A
.
String literals are lvalues, so "hello"
is an lvalue of type array of 6
const char
s (including the null terminator). This is distinct from other
literals, which are prvalues.
Finally, a named object declared with an rvalue reference (declared with &&
)
is also an lvalue. This is probably the most confusing of the rules, if for no
other reason than that it is called an rvalue reference. The name is just
there to indicate that it can bind to an rvalue (see later); once you've
declared a variable and given it a name it's an lvalue. This is most commonly
encountered in function parameters. For example:
void foo(A&& a){
}
Within foo
, a
is an lvalue (of type A
), but it will only bind to rvalues.
xvalues
An xvalue is an eXpiring value: an unnamed objects that is soon to be destroyed. xvalues may be either treated as glvalues or as rvalues depending on context.
xvalues are slightly unusual in that they usually only arise through explicit
casts and function calls. If an expression is cast to an rvalue reference to
some type T
then the result is an xvalue of type
T
. e.g. static_cast<A&&>(v1)
yields an xvalue of type A
.
Similarly, if the return type of a function is an rvalue reference to some
type T
then the result is an xvalue of type T
. This is the case with
std::move()
, which is declared as:
template <typename T>
constexpr remove_reference_t<T>&&
move(T&& t) noexcept;
Thus std::move(v1)
is an xvalue of type A
— in this case, the type
deduction rules deduce T
to be A&
since v1
is an lvalue, so
the return type is declared to be A&&
as remove_reference_t<A&>
is just A
.
The only other way to get an xvalue is by accessing a member of an
rvalue. Thus expressions that access members of temporary objects
yield xvalues, and the expression B().a
is an xvalue of type A
, since
the temporary object B()
is a prvalue. Similarly,
std::move(v2).a
is an xvalue, because std::move(v2)
is an xvalue, and
thus an rvalue.
prvalues
A prvalue is a Pure rvalue; an rvalue that is not an xvalue.
Literals other than string literals (which are lvalues) are
prvalues. So 42
is a prvalue of type int
, and 3.141f
is a prvalue
of type float
.
Temporaries are also prvalues. Thus given the definition of A
above, the
expression A()
is a prvalue of type A
. This applies to all temporaries:
any temporaries created as a result of implicit conversions are thus also
prvalues. You can therefore write the following:
int consume_string(std::string&& s);
int i=consume_string("hello");
as the string literal "hello"
will implicitly convert to a temporary of type
std:string
, which can then bind to the rvalue reference used for the
function parameter, because the temporary is a prvalue.
Reference binding
Probably the biggest difference between lvalues and rvalues is in how they bind to references, though the differences in the type deduction rules can have a big impact too.
There are two types of references in C++: lvalue references, which are
declared with a single ampersand, e.g. T&
, and rvalue references which are
declared with a double ampersand, e.g. T&&
.
lvalue references
A non-const
lvalue reference will only bind to non-const
lvalues of the same type, or a class derived from the referenced
type.
struct C:A{};
int i=42;
A a;
B b;
C c;
const A ca{};
A& r1=a;
A& r2=c;
//A& r3=b; // error, wrong type
int& r4=i;
// int& r5=42; // error, cannot bind rvalue
//A& r6=ca; // error, cannot bind const object to non const ref
A& r7=r1;
// A& r8=A(); // error, cannot bind rvalue
// A& r9=B().a; // error, cannot bind rvalue
// A& r10=C(); // error, cannot bind rvalue
A const
lvalue reference on the other hand will also bind to
rvalues, though again the object bound to the reference must have
the same type as the referenced type, or a class derived from the referenced
type. You can bind both const
and non-const
values to a const
lvalue
reference.
const A& cr1=a;
const A& cr2=c;
//const A& cr3=b; // error, wrong type
const int& cr4=i;
const int& cr5=42; // rvalue can bind OK
const A& cr6=ca; // OK, can bind const object to const ref
const A& cr7=cr1;
const A& cr8=A(); // OK, can bind rvalue
const A& cr9=B().a; // OK, can bind rvalue
const A& cr10=C(); // OK, can bind rvalue
const A& cr11=r1;
If you bind a temporary object (which is a prvalue) to a const
lvalue reference, then the lifetime of that temporary is extended to the
lifetime of the reference. This means that it is OK to use r8
, r9
and r10
later in the code, without running the undefined behaviour that would otherwise
accompany an access to a destroyed object.
This lifetime extension does not extend to references initialized from the first
reference, so if a function parameter is a const
lvalue reference, and gets bound
to a temporary passed to the function call, then the temporary is destroyed when
the function returns, even if the reference was stored in a longer-lived
variable, such as a member of a newly constructed object. You therefore need to
take care when dealing with const
lvalue references to ensure that you
cannot end up with a dangling reference to a destroyed temporary.
volatile
and const volatile
lvalue references are much less interesting,
as volatile
is a rarely-used qualifier. However, they essentially behave as
expected: volatile T&
will bind to a volatile
or non-volatile
, non-const
lvalue of type T
or a class derived from T
, and volatile const T&
will bind to any lvalue of type T
or a class derived from
T
. Note that volatile const
lvalue references do not bind to rvalues.
rvalue references
An rvalue reference will only bind to rvalues of the same
type, or a class derived from the referenced type. As for lvalue references,
the reference must be const
in order to bind to a const
object, though
const
rvalue references are much rarer than const
lvalue references.
const A make_const_A();
// A&& rr1=a; // error, cannot bind lvalue to rvalue reference
A&& rr2=A();
//A&& rr3=B(); // error, wrong type
//int&& rr4=i; // error, cannot bind lvalue
int&& rr5=42;
//A&& rr6=make_const_A(); // error, cannot bind const object to non const ref
const A&& rr7=A();
const A&& rr8=make_const_A();
A&& rr9=B().a;
A&& rr10=C();
A&& rr11=std::move(a); // std::move returns an rvalue
// A&& rr12=rr11; // error rvalue references are lvalues
rvalue references extend the lifetime of temporary objects in the same way
that const
lvalue references do, so the temporaries associated with rr2
,
rr5
, rr7
, rr8
, rr9
, and rr10
will remain alive until the
corresponding references are destroyed.
Implicit conversions
Just because a reference won't bind directly to the value of an expression, doesn't mean you can't initialize it with that expression, if there is an implicit conversion between the types involved.
For example, if you try and initialize a reference-to-A
with a D
object, and
D
has an implicit conversion operator that returns an A&
then all is well,
even though the D
object itself cannot bind to the reference: the reference is
bound to the result of the conversion operator.
struct D{
A a;
operator A&() {
return a;
}
};
D d;
A& r=d; // reference bound to result of d.operator A&()
Similarly, a const
lvalue-reference-to-E
will bind to an A
object if A
is implicitly convertible to E
, such as with a conversion constructor. In this
case, the reference is bound to the temporary E
object that results from the
conversion (which therefore has its lifetime extended to match that of the
reference).
struct E{
E(A){}
};
const E& r=A(); // reference bound to temporary constructed with E(A())
This allows you to pass string literals to functions taking std::string
by
const
reference:
void foo(std::string const&);
foo("hello"); // ok, reference is bound to temporary std::string object
Other Properties
Whether or not an expression is an lvalue or rvalue can affect a few other aspects of your program. These are briefly summarised here, but the details are out of scope of this article.
In general, rvalues cannot be modified, nor can they have their
address taken. This means that simple expressions like A()=something
or &A()
or &42
are ill-formed. However, you can call member functions on
rvalues of class type, so X().do_something()
is valid (assuming
class X
has a member function do_something
).
Class member functions can be tagged with ref qualifiers to indicate that they
can be applied to only rvalues or only lvalues. ref
qualifiers can be combined with const
, and can be used to distinguish
overloads, so you can define different implementations of a function depending
whether the object is an lvalue or rvalue.
When the type of a variable or function template parameter is deduce from its initializer, whether the initializer is an lvalue or rvalue can affect the deduced type.
End Note
rvalues and lvalues are a core part of C++, so understanding them is essential. Hopefully, this article has given you a greater understanding of their properties, and how they relate to the rest of C++.
If you liked this article, please share with the buttons below. If you have any questions or comments, please submit them using the comment form.
Posted by Anthony Williams
[/ cplusplus /] permanent link
Tags: cplusplus, lvalues, rvalues
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
C++ Concurrency in Action now available in Chinese!
Wednesday, 03 February 2016
Last week there was considerable excitement in my house as I received my copies of the Chinese translation of my book. The code looks the same, and they spelled my name correctly, but that's all I can tell. I can't read a word of Chinese, so I hope the content has translated OK, and doesn't read like it's been run through automatic translation software.
It's a great feeling to know that my book is going to reach a wider audience, joining the ranks of the C++ books available in Chinese. As Bjarne commented when I posted on Facebook, "we are getting there".
Posted by Anthony Williams
[/ news /] permanent link
Tags: C++, concurrency, multithreading, book
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Design and Content Copyright © 2005-2024 Just Software Solutions Ltd. All rights reserved. | Privacy Policy