I noticed that std::string's (really std::basic_string's) move assignment operator is noexcept. That makes sense to me. But then I noticed that none of the standard containers (e.g., std::vector, std::deque, std::list, std::map) declares its move assignment operator noexcept. That makes less sense to me. A std::vector, for example, is typically implemented as three pointers, and pointers can certainly be move-assigned without throwing an exception. Then I thought that maybe the problem is with moving the container's allocator, but std::string's have allocators, too, so if that were the issue, I'd expect it to affect std::string.
So why is std::string's move assignment operator noexcept, yet the move assignment operators for the standard containers are not?
I believe we're looking at a standards defect. The noexcept specification, if it is to be applied to the move assignment operator, is somewhat complicated. And I believe this statement to be true whether we are talking about basic_string or vector.
Based on [container.requirements.general]/p7 my English translation of what a container move assignment operator is supposed to do is:
C& operator=(C&& c)
If alloc_traits::propagate_on_container_move_assignment::value is
true, dumps resources, move assigns allocators, and transfers
resources from c.
If
alloc_traits::propagate_on_container_move_assignment::value is false
and get_allocator() == c.get_allocator(), dumps resources, and transfers
resources from c.
If
alloc_traits::propagate_on_container_move_assignment::value is false
and get_allocator() != c.get_allocator(), move assigns each c[i].
Notes:
alloc_traits refers to allocator_traits<allocator_type>.
When alloc_traits::propagate_on_container_move_assignment::value is true the move assignment operator can be specified noexcept because all it is going to is deallocate current resources and then pilfer resources from the source. Also in this case, the allocator must also be move assigned, and that move assignment must be noexcept for the container's move assignment to be noexcept.
When alloc_traits::propagate_on_container_move_assignment::value is false, and if the two allocators are equal, then it is going to do the same thing as #2. However one doesn't know if the allocators are equal until run time, so you can't base noexcept on this possibility.
When alloc_traits::propagate_on_container_move_assignment::value is false, and if the two allocators are not equal, then one has to move assign each individual element. This may involve adding capacity or nodes to the target, and thus is intrinsically noexcept(false).
So in summary:
C& operator=(C&& c)
noexcept(
alloc_traits::propagate_on_container_move_assignment::value &&
is_nothrow_move_assignable<allocator_type>::value);
And I see no dependence on C::value_type in the above spec and so I believe it should apply equally well to std::basic_string despite C++11 specifying otherwise.
Update
In the comments below Columbo correctly points out that things have gradually been changing all the time. My comments above are relative to C++11.
For the draft C++17 (which seems stable at this point) things have changed somewhat:
If alloc_traits::propagate_on_container_move_assignment::value is true, the spec now requires the move assignment of the allocator_type to not throw exceptions (17.6.3.5 [allocator.requirements]/p4). So one no longer needs to check is_nothrow_move_assignable<allocator_type>::value.
alloc_traits::is_always_equal has been added. If this is true, then one can determine at compile time that point 3 above can not throw because resources can be transferred.
So the new noexcept spec for containers could be:
C& operator=(C&& c)
noexcept(
alloc_traits::propagate_on_container_move_assignment{} ||
alloc_traits::is_always_equal{});
And, for std::allocator<T>, alloc_traits::propagate_on_container_move_assignment{} and alloc_traits::is_always_equal{} are both true.
Also now in the C++17 draft, both vector and string move assignment carry exactly this noexcept specification. However the other containers carry variations of this noexcept specification.
The safest thing to do if you care about this issue is to test explicit specializations of containers you care about. I've done exactly that for container<T> for VS, libstdc++ and libc++ here:
http://howardhinnant.github.io/container_summary.html
This survey is about a year old, but as far as I know is still valid.
I think the reasoning for this goes like this.
basic_string only works with non-array POD types. As such, their destructors must be trivial. This means that if you do a swap for move-assignment, it doesn't matter as much to you that the original contents of the moved-to string haven't been destroyed yet.
Whereas containers (basic_string is not technically a container by the C++ spec) can contain arbitrary types. Types with destructors, or types that contain objects with destructors. This means that it is more important to the user to maintain control over exactly when an object is destroyed. It specifically states that:
All existing elements of a [the moved-to object] are either move assigned to or destroyed.
So the difference does make sense. You can't make move-assignment noexcept once you start deallocating memory (through the allocator) because that can fail via exception. Thus, once you start requiring that memory is deallocated on move-assign, you give up being able to enforce noexcept.
The move assignment operator in container classes is defined to be noexcept because many containers are designed to implement the strong exception safety guarantee. Containers implement the strong exception safety guarantee because back before there were move assignment operators, the container had to be copied. If anything went wrong with the copy, the new storage was deleted and the container remained unchanged. Now we're stuck with that behavior. If the move assignment op is not noexcept, the slower copy assignment operator is invoked instead.
Related
Move operations should be noexcept; in the first place for intuitive and reasonable semantics. The second argument is runtime performance. From the Core Guidelines, C.66, "Make move operations noexcept":
A throwing move violates most people’s reasonably assumptions. A non-throwing move will be used more efficiently by standard-library and language facilities.
The canonical example for the performance-part of this guideline is the case when std::vector::push_back or friends need to grow the buffer. The standard requires a strong exception guarantee here, and this can only move-construct the elements into the new buffer if this is noexcept - otherwise, it must be copied. I get that, and the difference is visible in benchmarks.
However, apart from this, I have a hard time finding real-world evidence of the positive performance impact of noexcept move semantics. Skimming through the standard library (libcxx + grep), we see that std::move_if_noexcept exists, but it's almost not used within the library itself. Similarly, std::is_noexcept_swappable is merely used for fleshing out conditional noexcept qualifiers. This doesn't match existing claims, for example this one from "C++ High Performance" by Andrist and Sehr (2nd ed., p. 153):
All algorithms use std::swap() and std::move() when moving elements around, but only if the move constructor and move assignment are marked noexcept. Therefore, it is important to have these implemented for heavy objects when using algorithms. If they are not available and exception free, the elements will be copied instead.
To break my question into pieces:
Are there code paths in the standard library similar to the std::vector::push_back, that run faster when fed with std::is_nothrow_move_constructible types?
Am I correct to conclude that the cited paragraph from the book is not correct?
Is there an obvious example for when the compiler will reliably generate more runtime-efficient code when a type adheres to the noexcept guideline?
I know the third one might be a bit blurry. But if someone could come up with a simple example, this would be great.
Background: I refer to std::vector's use of noexcept as "the vector pessimization." I claim that the vector pessimization is the only reason anyone ever cared about putting a noexcept keyword into the language. Furthermore, the vector pessimization applies only to the element type's move constructor. I claim that marking your move-assignment or swap operations as noexcept has no "in-game effect"; leaving aside whether it might be philosophically satisfying or stylistically correct, you shouldn't expect it to have any effect on your code's performance.
Let's check a real library implementation and see how close I am to wrong. ;)
Vector reallocation. libc++'s headers use move_if_noexcept only inside __construct_{forward,backward}_with_exception_guarantees, which is used only inside vector reallocation.
Assignment operator for variant. Inside __assign_alt, the code tag-dispatches on is_nothrow_constructible_v<_Tp, _Arg> || !is_nothrow_move_constructible_v<_Tp>. When you do myvariant = arg;, the default "safe" approach is to construct a temporary _Tp from the given arg, and then destroy the currently emplaced alternative, and then move-construct that temporary _Tp into the new alternative (which hopefully won't throw). However, if we know that the _Tp is nothrow-constructible directly from arg, we'll just do that; or, if _Tp's move-constructor is throwing, such that the "safe" approach isn't actually safe, then it's not buying us anything and we'll just do the fast direct-construction approach anyway.
Btw, the assignment operator for optional does not do any of this logic.
Notice that for variant assignment, having a noexcept move constructor actually hurts (unoptimized) performance, unless you have also marked the selected converting constructor as noexcept! Godbolt.
(This experiment also turned up an apparent bug in libstdc++: #99417.)
string appending/inserting/assigning. This is a surprising one. string::append makes a call to __append_forward_unsafe under a SFINAE check for __libcpp_string_gets_noexcept_iterator. When you do s1.append(first, last), we'd like to do s1.resize(s1.size() + std::distance(first, last)) and then copy into those new bytes. However, this doesn't work in three situations: (1) If first, last point into s1 itself. (2) If first, last are exactly input_iterators (e.g. reading from an istream_iterator), such that it's known impossible to iterate the range twice. (3) If it's possible that iterating the range once could put it into a bad state where iterating the second time would throw. That is, if any of the operations in the second loop (++, ==, *) are non-noexcept. So in any of those three situations, we take the "safe" approach of constructing a temporary string s2(first, last) and then s1.append(s2). Godbolt.
I would bet money that the logic controlling this string::append optimization is incorrect. (EDIT: yes, it is.) See "Attribute noexcept_verify" (2018-06-12). Also observe in that godbolt that the operation whose noexceptness matters to libc++ is rv == rv, but the one it actually calls inside std::distance is lv != lv.
The same logic applies even harder in string::assign and string::insert. We need to iterate the range while modifying the string. So we need either a guarantee that the iterator operations are noexcept, or a way to "back out" our changes when an exception is thrown. And of course for assign in particular, there's not going to be any way to "back out" our changes. The only solution in that case is to copy the input range into a temporary string and then assign from that string (because we know string::iterator's operations are noexcept, so they can use the optimized path).
libc++'s string::replace does not do this optimization; it always copies the input range into a temporary string first.
function SBO. libc++'s function uses its small buffer only when the stored callable object is_nothrow_copy_constructible (and of course is small enough to fit). In that case, the callable is treated as a sort of "copy-only type": even when you move-construct or move-assign the function, the stored callable will be copy-constructed, not move-constructed. function doesn't even require that the stored callable be move-constructible at all!
any SBO. libc++'s any uses its small buffer only when the stored callable object is_nothrow_move_constructible (and of course is small enough to fit). Unlike function, any treats "move" and "copy" as distinct type-erased operations.
Btw, libc++'s packaged_task SBO doesn't care about throwing move-constructors. Its noexcept move-constructor will happily call the move-constructor of a user-defined callable: Godbolt. This results in a call to std::terminate if the callable's move-constructor ever actually does throw. (Confusingly, the error message printed to the screen makes it look as if an exception is escaping out the top of main; but that's not actually what's happening internally. It's just escaping out the top of packaged_task(packaged_task&&) noexcept and being halted there by the noexcept.)
Some conclusions:
To avoid the vector pessimization, you must declare your move-constructor noexcept. I still think this is a good idea.
If you declare your move-constructor noexcept, then to avoid the "variant pessimization," you must also declare all your single-argument converting constructors noexcept. However, the "variant pessimization" merely costs a single move-construct; it does not degrade all the way into a copy-construct. So you can probably eat this cost safely.
Declaring your copy constructor noexcept can enable small-buffer optimization in libc++'s function. However, this matters only for things that are (A) callable and (B) very small and (C) not in possession of a defaulted copy constructor. I think this describes the empty set. Don't worry about it.
Declaring your iterator's operations noexcept can enable a (dubious) optimization in libc++'s string::append. But literally nobody cares about this; and besides, the optimization's logic is buggy anyway. I'm very much considering submitting a patch to rip out that logic, which will make this bullet point obsolete. (EDIT: Patch submitted, and also blogged.)
I'm not aware of anywhere else in libc++ that cares about noexceptness. If I missed something, please tell me! I'd also be very interested to see similar rundowns for libstdc++ and Microsoft.
vector push_back, resize, reserve, etc is very important case, as it is expected to be the most used container.
Anyway, take look at std::fuction as well, I'd expect it to take advantage of noexcept move for small object optimization version.
That is, when functor object is small, and it has noexcept move constructor, it can be stored in a small buffer in std::function itself, not on heap. But if the functor doesn't have noexcept move constructor, it has to be on heap (and don't move when std::function is moved)
Overall, there ain't too many cases indeed.
I was writing some code that has a generic container that requires elements to be nothrow_move_constructible.
I decided to add a static_assert that enforces this, just in case.
To my surprise I can't compile now when using boost::container::flat_set.
I assumed that this was just an oversight and I need a more recent boost verison, but it seems that actually they deliberately made it not safely movable:
See docs here:
http://www.boost.org/doc/libs/1_61_0/doc/html/boost/container/flat_set.html
You can see that they did update it to use R-value references and marked swap as noexcept, but they chose not to make the move ctor noexcept.
It appears that move assignment is conditionally noexcept. The condition appears to depend on the value type and on the allocator in some way.
What could be the rationale for not being nothrow move constructible? Is it just an oversight?
If the objects within a container are not nothrow_move_constructible then it is very dangerous to take an entire set of one container and relocate it to another under certain conditions (usually involving Allocators). If two containers were not constructed with the same allocator, then it is no longer safe to move memory from one container to another (think two containers from two different memory arenas).
Digging in to the current source, both the contract and the implementation are problematic:
//! <b>Effects</b>: Move constructs a flat_map.
//! Constructs *this using x's resources.
flat_map(BOOST_RV_REF(flat_map) x)
: m_flat_tree(boost::move(x.m_flat_tree))
{ ... }
So your current expectation that it could currently by nothrow is correct. But what they are doing is probably not right.
I can only guess that they are worried that they will have to do revisit this in the future and don't want to have to weaken the nothrow contract later.
The C++11 std::allocator_traits template is used to query an Allocator to determine whether propagate_on_copy_assignment and propagate_on_move_assignment is true. These values affect how Container types must implement copy and move assignment. If std::allocator_traits<Allocator>::propagate_on_move_assignment == true then a Container move assignment operator must move assign its internal allocator object using the allocator contained in the RHS Container object.
Presumably, the point of this is that so we can implement Allocator types that can inform the client Container whether or not a move or copy operation should require that we copy any internal state inside the Allocator.
So... why do these typedefs only apply to assignment. Why do we not have propagate_on_copy_construct and propagate_on_move_construct? If an allocator has some internal state, shouldn't the client Container object be aware of that so it knows whether or not it should copy/move the allocator?
In assignment one has two choices:
Keep your existing allocator.
Copy/move from the rhs allocator.
The propagate_on_assign traits controls the choice for the assignment operators.
For construction, choice 1 (keeping your existing allocator) is not an option. There is no existing allocator. Instead your choices are:
Construct an allocator from nothing.
Copy/move from the rhs allocator.
Copy/move from some other source.
Choice one might be considered: default construct an allocator. Choice 3 is pretty vague, but different allocator schemes can be unpredictable and it is good to give the allocator designer as much flexibility as possible: Possibly to do some things that the committee never anticipated.
In an attempt to satisfy all three choices, at least for the container copy constructor, a new allocator_traits "switch" was designed:
Alloc select_on_container_copy_construction(const Alloc& rhs);
allocator_traits will return rhs. select_on_container_copy_construction() if that expression is well-formed. Otherwise it will return rhs. And container copy constructors are required to use allocator_traits<A>:: select_on_container_copy_construction(a) when constructing the lhs allocator during copy construction.
For allocator designers who just want their allocator copied during copy construction (choice 2), they don't have to do anything.
Allocator designers who want to have the lhs allocator default constructed on container copy construction just need to write the following member function for their allocator:
Alloc select_on_container_copy_construction() const {return Alloc{};}
And if they think of something clever for choice 3, they can probably implement it using the same hook as for choice 1.
So for copy construction, there is no need for a propagate_on trait as the select_on_container_copy_construction already covers all bases.
For move construction, see the bottom of page 12 of N2982:
Note that there is no select_on_container_move_construction()
function. After some consideration, we decided that a move
construction operation for containers must run in constant-time and
not throw, as per issue 1166.
Indeed this paper, and papers preceding it (that it references) are a good source of information for the rationale behind the C++11 allocator design. This paper even references a allocator_propagate_on_copy_construction from a previous paper, which subsequently evolved into select_on_container_copy_construction().
I am getting a crash when trying to move a std::vector<T> where T is clearly not movable (no move constructor/assignment operator was defined, and it contains internal pointers)
But why would the move functions of vector want to call the move functions of T? It should not be necessary.
So my question from the title: Is a std::vector<T> movable if T is not movable?
Yes, std::vector<T> is movable even if T is not movable. The left side merely takes ownership from the vector on the right, no elements are touched. (With one exception, listed in #2)
The move assignment of a vector would only call the move constructor or move assignment of T if the and their allocators compare as equal and the left side's allocator's propagate_on_container_move_assignment is false. (Note that if your move constructor might throw or doesn't exist, the copy constructor will be used instead) However, it is unlikely that you are encountering either of these.
Reworded: if propagate_on_container_move_assignment is true (it usually is), then the vector is always movable, and does so without touching individual elements. If it's false, but the allocators compare equal, the vector will be moved without touching individual elements. If it's false and the allocators compare inequal, the individual elements will be transferred. If a nothrow move assignment exists, that's used. Otherwise, if a copy constructor exists, that's used. Otherwise the throwing move assignment is used. If it's neither movable nor copiable, then that's undefined behavior.
Having no move assignment operator defined, and the class containing internal pointers, does not mean your class is not movable. In fact, it sounds like the compiler thinks it is movable. If you want to disable moves, use T(T&&) = delete; and T& operator=(T&&) =delete, but I don't recommend that. Instead, add a correctly working move constructor and move assignemnt. They tend to be easy, and quite useful.
Table 99 in [container.requirements.general] of n3797 about "Allocator-aware container requirements" says about move construction:
Requires: move construction of A shall not exit via an exception.
Where A is the allocator type. It does not require MoveInsertable for the value type.
The time complexity requirement is: "constant", btw.
std::vector typically stores some pointers plus the allocator. When move-constructing a std::vector, only pointers and the allocator have to be moved. The elements don't need to be touched.
I'm developing a container-like class and I would like to make use of the standard allocator infrastructure much like the standard containers. On the net I find a lot of material about how to use the std::allocator class alone, or how to define a custom allocator for standard containers, but the material about how to make generically use of an standard conforming allocator is very rare, in particular in the context of C++11, where things seem to be much easier from the point of view of who writes a custom allocator, but more complex from the container's point of view.
So my question is about how to correctly make use of a standard conforming allocator in the most generic way, specifically:
First of all, when should I design a custom container in this way? Is there a sensible performance overhead (including missing optimization opportunities) in using the default allocator instead of plain new/delete?
Do I have to explicitly call contained objects' destructors?
How do I discriminate between stateful and stateless allocators?
How to handle stateful allocators?
When (if ever) are two instances interchangeable (when can I destroy with one instance the memory allocated with another one)?
They have to be copied when the container is copied?
They can/have to be moved when the container is moved?
In the container's move constructor and move assignment operator, when can I move the pointer to allocated memory, and when do I have to allocate different memory and move the elements instead?
Are there issues about exception safety in this context?
I'm specifically interested in an answer about the C++11 world (does it change anything in C++14?)
In all answers below, I'm assuming you want to follow the rules for C++11 std-defined containers. The standard does not require you to write your custom containers this way.
First of all, when should I design a custom container in this way? Is there a sensible performance overhead (including missing
optimization opportunities) in using the default allocator instead of
plain new/delete?
One of the most common and effective uses for custom allocators is to have it allocate off of the stack, for performance reasons. If your custom container can not accept such an allocator, then your clients will not be able to perform such an optimization.
Do I have to explicitly call contained objects' destructors?
You have to explicitly call allocator_traits<allocator_type>::destroy(alloc, ptr), which in turn will either directly call the value_type's destructor, or will call the destroy member of the allocator_type.
How do I discriminate between stateful and stateless allocators?
I would not bother. Just assume the allocator is stateful.
How to handle stateful allocators?
Follow the rules laid out in C++11 very carefully. Especially those for allocator-aware containers specified in [container.requirements.general]. The rules are too numerous to list here. However I'm happy to answer specific questions on any of those rules. But step one is get a copy of the standard, and read it, at least the container requirements sections. I recommend the latest C++14 working draft for this purpose.
When (if ever) are two instances interchangeable (when can I destroy with one instance the memory allocated with another one)?
If two allocators compare equal, then either can deallocate pointers allocated from the other. Copies (either by copy construction or copy assignment) are required to compare equal.
They have to be copied when the container is copied?
Search the standard for propagate_on and select_on_container_copy_construction for the nitty gritty details. The nutshell answer is "it depends."
They can/have to be moved when the container is moved?
Have to be for move construction. Move assignment depends on propagate_on_container_move_assignment.
In the container's move constructor and move assignment operator, when can I move the pointer to allocated memory, and when do I have to allocate different memory
and move the elements instead?
The newly move constructed container should have gotten its allocator by move constructing the rhs's allocator. These two allocators are required to compare equal. So you can transfer memory ownership for all allocated memory for which your container has a valid state for that pointer being nullptr in the rhs.
The move assignment operator is arguably the most complicated: The behavior depends on propagate_on_container_move_assignment and whether or not the two allocators compare equal. A more complete description is below in my "allocator cheat sheet."
Are there issues about exception safety in this context?
Yes, tons. [allocator.requirements] lists the allocator requirements, which the container can depend on. This includes which operations can and can not throw.
You will also need to deal with the possibility that the allocator's pointer is not actually a value_type*. [allocator.requirements] is also the place to look for these details.
Good luck. This is not a beginner project. If you have more specific questions, post them to SO. To get started, go straight to the standard. I am not aware of any other authoritative source on the subject.
Here is a cheat-sheet I made for myself which describes allocator behavior, and the container's special members. It is written in English, not standard-eze. If you find any discrepancies between my cheat sheet, and the C++14 working draft, trust the working draft. One known discrepancy is that I've added noexcept specs in ways the standard has not.
Allocator behavior:
C() noexcept(is_nothrow_default_constructible<allocator_type>::value);
C(const C& c);
Gets allocator from alloc_traits::select_on_container_copy_construction(c).
C(const C& c, const allocator_type& a);
Gets allocator from a.
C(C&& c)
noexcept(is_nothrow_move_constructible<allocator_type>::value && ...);
Gets allocator from move(c.get_allocator()), transfers resources.
C(C&& c, const allocator_type& a);
Gets allocator from a. Transfers resources if a == c.get_allocator().
Move constructs from each c[i] if a != c.get_allocator().
C& operator=(const C& c);
If alloc_traits::propagate_on_container_copy_assignment::value is true,
copy assigns allocators. In this case, if allocators are not equal prior
to assignment, dumps all resources from *this.
C& operator=(C&& c)
noexcept(
allocator_type::propagate_on_container_move_assignment::value &&
is_nothrow_move_assignable<allocator_type>::value);
If alloc_traits::propagate_on_container_move_assignment::value is true,
dumps resources, move assigns allocators, and transfers resources from c.
If alloc_traits::propagate_on_container_move_assignment::value is false
and get_allocator() == c.get_allocator(), dumps resources, and transfers
resources from c.
If alloc_traits::propagate_on_container_move_assignment::value is false
and get_allocator() != c.get_allocator(), move assigns each c[i].
void swap(C& c)
noexcept(!allocator_type::propagate_on_container_swap::value ||
__is_nothrow_swappable<allocator_type>::value);
If alloc_traits::propagate_on_container_swap::value is true, swaps
allocators. Regardless, swaps resources. Undefined behavior if the
allocators are unequal and propagate_on_container_swap::value is false.