File size

File size

File size

File size

File size

211.3 MB

Welcome to another installment of C9 Lectures covering the powerful general C++ library, STL. Joining us once again is the great Stephan T. Lavavej, Microsoft's keeper of the STL cloth (this means he manages the partnership between the owners of STL (dinkumware) and Microsoft, including, of course, bug fixes and enhancements to the STL that ships as part of Visual C++). Simply, Stephan is a C++ library developer.

As is Stephan's nature, he elaborates on technical details in very substantive way. The Standard Template Library, orSTL, is a C++ library of container classes, algorithms, and iterators. STL provides many fundamental algorithms and data structures. Furthermore, the STL is a general-purpose library: its components are heavily parameterized, such that almost every component in the STL is a template.

In part 9, Stephan introduces us to rvalue references, a C++0x language feature, which enables two different things: move semantics and perfect forwarding. It's all about performance, brothers and sisters!

Awesome video! The only thing I missed was discussing what T&& means when T is a template parameter, but maybe explaining reference collapsing rules would have taken too much time... anyway, what will the next video be about?

That's subtly different. In my implementation, the assignment destination's original resources are destroyed immediately. In your implementation, the assignment destination's original resources are swapped into the assignment source. This doesn't usually matter for two reasons:

1. When the assignment source is a temporary, it's going to be destroyed very soon ("at the semicolon"). However, it could be std::move(lvalue), in which case it might survive for a long time until reassignment or destruction.

2. When the resources consist purely of memory, usually nobody will notice if we hold on to memory a little longer than absolutely necessary. However, the memory involved could be significant, or non-memory resources could easily be involved.

Because I prefer to avoid subtle gotchas, I implement move assignment so that, like copy assignment, it immediately destroys the assignment destination's original resources.

> The only thing I missed was discussing what T&& means when T is a template parameter,> but maybe explaining reference collapsing rules would have taken too much time...

I would have liked to get to perfect forwarding, but I ran out of time. 45 minutes isn't very much time.

> anyway, what will the next video be about?

I'm open to suggestions. I'd like to cover simple techniques before advanced ones (looking at the STL's implementation would be advanced). I think I might demonstrate <type_traits>. (Note that I won't be covering iostreams in this series.)

I'm open to suggestions. I'd like to cover simple techniques before advanced ones (looking at the STL's implementation would be advanced). I think I might demonstrate <type_traits>. (Note that I won't be covering iostreams in this series.)

#include <type_traits>, decltype, and the rest of the template metaprogramming (aka using C++ to "preprocess"/modify/script C++ during compile-time) material would be interesting. Any info on how to use std::string or char * ... or whatever ... for manipulating strings at compile-time in C++0x would be appreciated. If the new standard still does not support manipulating strings at compile-time without work-arounds (aka boost::mpl::string), then what is the current justification that the standards committee gives for still not allowing string manipulations at compile-time? For template-metaprogramming, I'd personnally like to see an example of loop-unrolling ... right now, if I need a for ( i = 0; i < n; ++i ) { v[i]=...; } but I just want a v[0]=...; v[1]=...; ...; v[n]=...;, then I just script that unrolled-loop to a text file and copy & paste ... which works ... but is a kludge. I'd like C++ to do that loop-unrolling for me at compile-time ... so I can share and reuse that C++ solution.

Also, generating random floats or doubles from a Normal distribution using #include <random> would be great ... esp. for simultations, stocastic models, and games. Any explanation on how to create our own distributions would also be great.

Actually STL, to me the usage of lvalues and rvalues being referred to as left & right makes sense when thought of to be similar to key-value pairs. With the left lvalue being the "key" and the rvalue being the "value" which lives at the lvalue. In this way of thinking about them, I will try to explain using a table, where lvalues are user defined and named, and the rvalues are the compiler managed values which are refrenced internally as temporaries with lifetimes managed by the compiler.

User defined named; lvalues which is our explicitly given name for a location:

As you can see, we can refer to our managed/uer-defined variables using our named location(lvalue) while on the other hand, the compiler generated temporaries arent usually cared about, where we only want their values that they contain(its rvalue). Even though lvalue could also mean location value and rvalue meaning return value, whichever way you like best, it IS possible to think of them being left & right in my opinion.

I must say, STL this video was the video I enjoyed the most of your 9 videos, even though it wasn't specifically aimed at an STL feature, because you finally delved into a more technical subject making this valuable.

Nice videos, I'm enjoying how clear and relaxed you teach the STL.Will be a nice have a indeph of <type_traits> for next video. The only time I ventured in <type_traits> was to use the aligned_storage class, the compiler declspec(align(#)), and <array> with the SSE intrinsics in some tests and it worked.

@Burkholder:Disregard my no-compiler-warning for C++ in Visual Studio 2010. I was compiling with Warning Level Level3, vice Warning Level Level4. Once I switched to Level4, I received the following warning

[HeavensRevenge]> where lvalues are user defined and named, and the rvalues are the compiler managed values> which are refrenced internally as temporaries with lifetimes managed by the compiler.

Sorry, this is deeply incorrect.

As I explained in the video, lvalueness/rvalueness is a property of *expressions*, not objects. It's possible to have lvalue expressions referring to temporary objects (for example, this happens whenever a temporary X is passed to a function taking const X&), and to have rvalue expressions referring to user-named objects (for example, std::move(foobar)).

> Even though lvalue could also mean location value and rvalue meaning return value, whichever way you like best

Historically, L meant "left side of an assignment" and R meant "right side of an assignment". As I explained in the video, this is no longer accurate (due to constness, among other things). Nor should you attempt to invent your own meanings for L and R.

I think of them like this: lvalues are "persistent" and rvalues are "ephemeral". I can say a[i] over and over, referring to something persistent, so it is an lvalue. Given const X& x, I can also say x over and over, so it is an lvalue. It doesn't matter if x is bound to a temporary object that will evaporate shortly after my function returns. For the duration of my function where x is in scope, I have something persistent. Conversely, y + z does not refer to something persistent, so it is an rvalue. std::move(foobar) is also an rvalue, because it's saying "I want you to pretend that foobar is going to evaporate soon".

> I must say, STL this video was the video I enjoyed the most of your 9 videos,> even though it wasn't specifically aimed at an STL feature, because you> finally delved into a more technical subject making this valuable.

I recall mentioning VC's Evil Extension in the video, and exhorting the audience to compile with /W4. If I didn't, I should have. This extension is the most disgusting, vile thing ever (the Standard prohibits this nonsense for extremely good reasons), it should never be used, and it's good that /W4 warns about it.

I am mostly ignorant of C++ (and even more so of 0X), so please forgive me if this makes no sense.

It seems to me that what is accomplished by "std::move" could (and perhaps should) be implied by how the code is written. Cannot the compiler reason that "here is the last place this reference is...referenced, therefore it is safe to use "move" semantics"?

Furthermore, supposing you specify "std::move", could not the compiler issue a strong warning or even an error, should the 'moved' reference be...referenced at a later point?

[quote]1 day ago, STL wrote[NotFredSafe]> Can't we simplify the move assignment operator as follows?> remote_integer& operator=(remote_integer&& other) { swap(other); return *this; }> I mean, swap is exception-safe anyway, right? Why the extra temporary?That's subtly different. In my implementation, the assignment destination's original resources are destroyed immediately. In your implementation, the assignment destination's original resources are swapped into the assignment source. This doesn't usually matter for two reasons:1. When the assignment source is a temporary, it's going to be destroyed very soon ("at the semicolon"). However, it could be std::move(lvalue), in which case it might survive for a long time until reassignment or destruction.2. When the resources consist purely of memory, usually nobody will notice if we hold on to memory a little longer than absolutely necessary. However, the memory involved could be significant, or non-memory resources could easily be involved.Because I prefer to avoid subtle gotchas, I implement move assignment so that, like copy assignment, it immediately destroys the assignment destination's original resources.[/quote]

Sorry, I'm very confused, wont that extra temporary cause performance loss, like an extra copy or does the compiler optimize away this extra temporary ?

Thanks for another great lecture, STL, i'm already eagerly waiting for more. :)

It seems to me that what is accomplished by "std::move" could (and perhaps should) be implied by how the code is written. Cannot the compiler reason that "here is the last place this reference is...referenced, therefore it is safe to use "move" semantics"?

I found a text made by Thomas Becker a good companion while re-watching de video, it is dated from July/2010, the text have 10 pages and try to explain de rvalue, move, and forward step by step.[url]http://thbecker.net/articles/rvalue_references/section_01.html[/url]

[NotFredSafe]> Any particular reason you did not mention the distinction between prvalues and xvalues?

I suspected that explaining the lvalue/xvalue/prvalue/glvalue/rvalue taxonomy would be more confusing than clarifying.

[dpratt71]> Cannot the compiler reason that "here is the last place this reference is...referenced, therefore it is safe to use "move" semantics"?

The compiler is very smart, but not omniscient. In particular, when it sees something like a memory allocation followed by a deallocation, it can't determine whether that work is unnecessary.

C++ compilers have always understood the distinction between lvalues and rvalues (in fact, even C compilers have this knowledge). What move semantics does is very clever: it provides just a little bit of extra information to the compiler in order to enable major optimizations that were previously impossible. This information takes two forms: telling the compiler to treat certain lvalues as rvalues with std::move(), and telling the compiler to do something more efficient during construction and assignment when the source is a modifiable rvalue.

> Furthermore, supposing you specify "std::move", could not the compiler issue a strong> warning or even an error, should the 'moved' reference be...referenced at a later point?

In general, moved-from objects are in a "valid but unspecified" state, so the only things that you should do to them are reassign them or destroy them. However, certain types provide stronger guarantees. For example, moved-from shared_ptrs are guaranteed to be empty.

It should not. The optimizer should be able to see through simple pointer assignments. In any event, this is just a matter of a few instructions. The major expense (unnecessary dynamic memory allocation/deallocation) has already been avoided.

[NotFredSafe]> If you return a local variable, the move is indeed implied.

Yes, due to a special rule in the Working Paper. Named local variables are lvalues, and ordinarily lvalues must be copied from, not moved from. However, returning from a function triggers the destruction of local variables. Since val's destruction is imminent, "return val;" will automatically move from val.

Please make the other video before Christmas (it'll be considered your gift to us ). Well, I'm still in for the Template Metaprogramming, but don't start too complicated, since I'm not that good at math.

Question, Video 3 you had the container traits etc. Since I'm not only using VS2010, I can't always use the "auto" keyword. How do I get to the iterator from the template argument? Will this work? It compiles, but I can't say if it can crash:

Your code assumes that type U directly has member functions begin() const and end() const, vice inheriting them. Your code also assumes that const_iterator comes directly from U, vice inheriting it. The C++ standard does __not__ assume this for associative containers like std::map and std::set (see section 23.6.1 point 2 [for std::map] and section 23.6.3 point 2 [for std::set] of the current draft of the C++ standard). According to the C++ standard, implementers are free to typedef std::map<...>::const_iterator or std::set<...>::const_iterator any way they want to ... so since Dinkumware uses inheritance to implement std::map and std::set, then Dinkumware can choose to just use the inherited std::_Tree<...>::const_iterator std::_Tree<...>::begin() const and std::_Tree<...>::const_iterator std::_Tree<...>::end () const. Visual Sudio uses Dinkumware's STL implementation ... while GNU/g++ does not. Dinkumware's std::map and std::set implementation uses inheritance ... while GNU/g++ does not. If GNU/g++ used this inherited structure, then the results from GNU/g++ and Visual Studio would be the same. Here's an example:

Because your code requires something that the C++ standard does not require, your code is not a good way to check for a container at compile time ... and is implementation dependent. You should change your code to reflect the C++ standard requirements.

Also, good info on Substitution Failure Is Not An Error (SFINAE) can be found on page 106 of C++ Templates: The Complete Guide by Vandevoorde and Josuttis.

Hope This Clarifies,Joshua Burkholder

P.S. - If you wanted to keep your previous structure and still be able to pick up the associative containers, std::set and std::map, then you could always use a partial specialization on your is_container<...> struct. Here's an example:

However, it could be std::move(lvalue), in which case it might survive for a long time until reassignment or destruction.[/quote]Hi STL,I'd think in the case of move assignment operator, std::move will never be called with lvalue, correct? Because the move assignment operator takes an rvalue reference, and rvalue reference cannot bind to lvalue reference.But the explanation for the subtle differences are very valid, thank you for explaining that.Thanks for the awesome videos!

[quote]a moment ago[quote] 6 days ago, STL wrote However, it could be std::move(lvalue), in which case it might survive for a long time until reassignment or destruction. [/quote] Hi STL, I'd think in the case of move assignment operator, std::move will never be called with lvalue, correct? Because the move assignment operator takes an rvalue reference, and rvalue reference cannot bind to lvalue reference. But the explanation for the subtle differences are very valid, thank you for explaining that. Thanks for the awesome videos![/quote]

Ah scratch that question! I understan now. Somebody could write assignment operator with std::move(lhs) in which case it will invoke move assignment operator and resources will be swapped to an lvalue..

NotFredSafe/Burkholder: Please note that it is technically forbidden to take the address of a Standard Library member function. (They can be overloaded, making &foo::bar ambiguous, and they can have additional default arguments, defeating attempts to disambiguate via static_cast.)

This is one of those things that the Standard doesn't explicitly say, but that logically follows from other Standardese. (This is the second most subtle way that the Standard specifies stuff. The MOST subtle way is "X is permitted because nothing prohibits it", which for example applies to v.push_back(v[0]).)

I'm filming Part 10, which will be the final part of this introductory series, on Thursday. They're renovating the studio this month, but we found a temporary location where I'll be able to have a screen but no whiteboard.

I hope that someone could provide me with elegant solution for this problem.

Let's say we have vector with dynamically allocated objects, and we want to use remove_if to delete not necessary objects.I wrote such template (code below), and as You figured, I learned the hard way that you cannot use iterator returned from remove_if as begin or iterate it :)So one solution that I though about, is that predicate should delete those elements that should be removed, but this is to easy to forget.All other toughs ended up creating second vector or iterating vector 2 times...So maybe You could provide some elegant solution for this problem.Thanks.

@DerayngerWell You see this example works grate if this container contains objects from stack, but if in container are objects on the heap (allocated with NEW) then this will leave memory leaks.One solution as I said would be to delete object in the predicate if condition is met.But I think that predicate should only determine if object should be removed, and NOT delete them. (well maybe this thinking is completely wrong and I should delete objects with predicate?).Or maybe there is some solution which wouldn't need to iterate container 2 times (one time to delete objects, second time to remove them from container).

Thanks. What exactly do you like about it the most? The fact that it's covering a newer and more advanced topic?

Short answer, yes.

The episodes I personally find most interesting are the ones that have taken a deeper dive into the library and/or have helped to introduce some of the newer (C++0x) aspects of the language.

In this episode in particular, I found that it:

- Introduced a feature (r-value references) that was mostly new to me

- Showed what they really do at a low level

- Showed me why I should care about them and how I can use them to solve problems in real code

- Gave clear examples of where and how to use them

- Let me see how they are applied within the library (and thus providing benefits) even if I am not directly using them in my code (improvements with only a recompile).

- Presented a more advanced topic that you don't find in the piles of beginner STL info out there.

And the most important part is that you have (as always) managed to present all of this using a very clear, uncomplicated, straight-to-the-point style. You really do have a knack for understandable presentation of oft-complex material. (Maybe you should write a book?)

Anyway, your description of episode 10 sounds interesting. I eagerly await that episode, as well as the advanced series. It sounds like there is even more goodness coming there.

[SauliusZ]> Let's say we have vector with dynamically allocated objects

As I believe I stressed in Part 3, every owning raw pointer (a T * that must be deleted) is a potential memory leak. Containers of owning raw pointers (e.g. vector<T *> where the pointers must be deleted) are basically guaranteed leaktrocity.

You should use either vector<shared_ptr<T>> or vector<unique_ptr<T>>. You should be using VC10, so both should be available to you. If you're using VC9 SP1 (old!) you'll have access to shared_ptr (in std::tr1) but not unique_ptr. If you're using VC8 SP1 (argh, REALLY OLD!), you can use boost::shared_ptr.

[ryanb]> The episodes I personally find most interesting are the ones that have taken a deeper dive into the library and/or have helped to introduce some of the newer (C++0x) aspects of the language.

Thanks for the explanation; this will help me to target future episodes more precisely.

> And the most important part is that you have (as always) managed to present all of this using a very clear, uncomplicated, straight-to-the-point style.> You really do have a knack for understandable presentation of oft-complex material.

:->

My ultimate goal for this series is to demonstrate that anyone can do this stuff if they approach it in the right way. It would be especially awesome to start a chain reaction - if I've helped you to understand something better, whether it's the STL or rvalue references or whatever, it's now your responsibility to spread this to other programmers and the codebases that you work on!

> (Maybe you should write a book?)

That's one of the things that I'd like to do. My understanding of C++ is still expanding, though. The more I learn, the more I realize how much else there is to learn.

> Anyway, your description of episode 10 sounds interesting.

I filmed it yesterday and it should be available in January. I spent a fair amount of time explaining SFINAE, which is a tricky Core Language concept.

> as well as the advanced series. It sounds like there is even more goodness coming there.

In the intro series, I assumed knowledge of C++ but not the STL's interface. In the advanced series, I'll assume knowledge of the STL's interface but not its implementation. I've spent a lot of time covering the basic concepts that I live and breathe all day (what's a vector, what's a shared_ptr, etc.). Now I'd like to try covering the most complicated stuff that I work with.

This penultimate lecture of the fantastic introductory series is excellent and I'm looking forward to your coverage of <type_traits>. You're a natural at getting across the salient points of your chosen topic.

I'm impressed that you "wing it" in most lectures when you often elaborate on the reasoning and history behind features and decisions with such clarity, this is rare.

Please keep recording lectures for as long as you enjoy doing so, I particularly look forward to an advanced series.

peteo: Thanks! It's a fair amount of work, but comments like yours help to keep me going. :->

> The next two years for C++

C++0x will officially become an International Standard, and compilers/libraries will increasingly support it. I can't show off what's coming in VC11, but I can walk through what we did in VC10.

> The Boost libraries

An episode about Boost is a good idea, and well-suited to the advanced series. One of the few downsides to my job, other than the fact that I get free Diet Mountain Dew but not free BBQ Popchips from the vending machines, is that I get very little time to use Boost. Other teams at MS use Boost, but the STL is the layer underneath Boost. Most of my Boost experience comes from college or from programming at home, so it's not exactly comprehensive. Still, things like Bimap, Filesystem, and Format (to name some things I've used and that aren't in C++0x) would be great to demonstrate.

> Template meta programming

Oh yes, you'll be seeing a lot of this.

> Algorithms and Big-O notation

I expect that I'll be going over some of this with the implementations of STL algorithms, although I've already given my Big-O spiel in the intro series.

> Stephan's guide to the C++ specification and community

The Working Paper is freely available (and the International Standard in PDF form is cheap). The Standard Library section is *mostly* intelligible to programmers who don't read Standardese all day, with the exception of iostreams/locales. As for the C++ community, there are probably a bunch of good sites I don't know about (and newsgroups; comp.lang.c++[.moderated] still seems to be active but I don't monitor it). Certainly I would say that Boost is the leading edge of C++ development.

This should come up as I'm looking through the STL's implementation; there are some good practices that we follow, places where we do stuff that ordinary users shouldn't do at all (like _Ugly identifiers) or should think carefully before doing (like some of the stuff we do with inheritance), and very few places where we do bad stuff that nobody should imitate and that we should fix (the most notorious example is using std::forward where we should use std::move).

> Compiler features, settings and design, debugging tips

Interestingly, I characterize myself as being very good at static code analysis and compile-time debugging, but lacking magic powers for run-time debugging. I try to, and usually succeed at, writing bulletproof code that works on the first try and doesn't need to be debugged. Conveying how to do this is kind of hard but I keep trying (and trying, and trying, even when people don't listen when I say that owning raw pointers are memory leaks waiting to happen, see above - my code is basically utterly immune to memory leaks because of how it is structured).

> I'd like to be able to download the code shown in this episode, commented code would be a plus.

I'm 99% sure that this is on my work laptop (I distinctly remember cleaning out old files, noticing my rvalue references examples on my desktop, and deciding to keep them because someone might ask for them). Unfortunately I'm on vacation right now, and my work laptop is the one thing that I don't have remote access to. I've written myself a todo to dig up this code when I get back after the new year. It was cleaned up and updated from my rvalue references v1 blog post.

I was always cautious about rvalues even since they've appeared few years ago in discussions. And this video demonstrates that very clearly to a common developer -- rvalue adds a LOT of complexity to the language while falling short of achieving true move semantics. It is a great improvement, there is no doubt about it. But see for yourself:- using move constructor (ctor) still results in calling temporary's destructor (dtor). No one needs that thing anymore, why wasting CPU cycles to check moved values for some magical NULL values? What if type has no such values? In reality all you need is reduced mctor (no need to touch temporary object) and "don't call original temporary's dtor" guarantee provided by environment.- proposed move operator (mop=) v1 implemented as (MyClass(move(ref)).swap(*this)), it's price is: mctor + swap + 2 dtors! But all you need is release all resources you have (dtor call) + the same "reduced mctor" + "forget temporary" guarantee. And btw -- "swap" is worse than "move", "move" is worse than "reduced mctor".- mop= v2 (i.e. universal op=) is the sameMy problem with rvalues is that they are so complex and give so much advantage over what we have in language now that at the end of the day no one will ever be able to add more efficient true move semantic to C++ -- there will be only few interested people and it will very likely contradict with complex clauses in standard created to support rvalues -- you'll have to remove it... but then it'll break existing code.So, in my opinion rvalues are a partial solution that will make sure that we never get a complete one. :-(

That's just a few instructions, and the compiler is much more likely to be able to see through them (it can't see through memory allocation/deallocation, but pointer assignments and tests are much friendlier to the optimizer).

> What if type has no such values?

Movable objects typically have resourceless states. Some objects don't, but that can be dealt with.

No, it is not. It is a function call (lets leave optimizer aside for a moment -- none of it's magic is guaranteed to happen anyway). And more -- move ctor has to be more expensive, move assignment -- even worse... C++ is usually quite proud of "not doing unnecessary work", but it is not the case with rvalues.

Remove this comment

Remove this thread

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation,
please create a new thread in our Forums, or
Contact Us and let us know.