this, and most also return *this so that the user-defined operators can be used in the same manner as the built-ins. However, in a user-defined operator overload, any type can be used as return type (including void). can be any type including
Explanation Builtin direct assignment Example Builtin compound assignment Example Defect reports See also |
copy assignment operator replaces the contents of the object a with a copy of the contents of b ( b is not modified). For class types, this is a special member function, described in copy assignment operator .
operator replaces the contents of the object with the contents of , avoiding copying if possible ( may be modified). For class types, this is a special member function, described in . | (since C++11) |
For non-class types, copy and move assignment are indistinguishable and are referred to as direct assignment .
compound assignment operators replace the contents of the object a with the result of a binary operation between the previous value of a and the value of b .
The direct assignment expressions have the form
lhs rhs | (1) | ||||||||
lhs | (2) | (since C++11) | |||||||
lhs rhs | (3) | (since C++11) | |||||||
For the built-in operator, lhs may have any non-const scalar type and rhs must be implicitly convertible to the type of lhs .
The direct assignment operator expects a modifiable lvalue as its left operand and an rvalue expression or a braced-init-list (since C++11) as its right operand, and returns an lvalue identifying the left operand after modification. The result is a bit-field if the left operand is a bit-field.
For non-class types, the right operand is first implicitly converted to the cv-unqualified type of the left operand, and then its value is copied into the object identified by left operand.
When the left operand has reference type, the assignment operator modifies the referred-to object.
If the left and the right operands identify overlapping objects, the behavior is undefined (unless the overlap is exact and the type is the same)
If the right operand is a has scalar type, {} is equivalent to E1 = T{}, where is the type of . {E2} is equivalent to E1 = T{E2}, where is the type of . has class type, the syntax E1 = {args...} generates a call to the assignment operator with the as the argument, which then selects the appropriate assignment operator following the rules of . Note that, if a non-template assignment operator from some non-class type is available, it is preferred over the copy/move assignment in because to non-class is an , which outranks the user-defined conversion from to a class type. | (since C++11) |
Using an lvalue of volatile-qualified non-class type as left operand of built-in direct assignment operator is deprecated, unless the assignment expression appears in an or is a . | (since C++20) |
In overload resolution against user-defined operators , for every type T , the following function signatures participate in overload resolution:
& operator=(T*&, T*); | ||
volatile & operator=(T*volatile &, T*); | ||
For every enumeration or pointer to member type T , optionally volatile-qualified, the following function signature participates in overload resolution:
operator=(T&, T); | ||
For every pair A1 and A2, where A1 is an arithmetic type (optionally volatile-qualified) and A2 is a promoted arithmetic type, the following function signature participates in overload resolution:
operator=(A1&, A2); | ||
[ edit ] builtin compound assignment.
The compound assignment expressions have the form
lhs op rhs | (1) | ||||||||
lhs op | (2) | (since C++11) | |||||||
lhs op rhs | (3) | (since C++11) | |||||||
op | - | one of *=, /= %=, += -=, <<=, >>=, &=, ^=, |= |
lhs | - | for the built-in operator, lhs may have any arithmetic type, except when op is += or -=, which also accept pointer types with the same restrictions as + and - |
rhs | - | for the built-in operator, rhs must be implicitly convertible to lhs |
The behavior of every builtin compound-assignment expression E1 op = E2 (where E1 is a modifiable lvalue expression and E2 is an rvalue expression or a braced-init-list (since C++11) ) is exactly the same as the behavior of the expression E1 = E1 op E2 , except that the expression E1 is evaluated only once and that it behaves as a single operation with respect to indeterminately-sequenced function calls (e.g. in f ( a + = b, g ( ) ) , the += is either not started at all or is completed as seen from inside g ( ) ).
In overload resolution against user-defined operators , for every pair A1 and A2, where A1 is an arithmetic type (optionally volatile-qualified) and A2 is a promoted arithmetic type, the following function signatures participate in overload resolution:
operator*=(A1&, A2); | ||
operator/=(A1&, A2); | ||
operator+=(A1&, A2); | ||
operator-=(A1&, A2); | ||
For every pair I1 and I2, where I1 is an integral type (optionally volatile-qualified) and I2 is a promoted integral type, the following function signatures participate in overload resolution:
operator%=(I1&, I2); | ||
operator<<=(I1&, I2); | ||
operator>>=(I1&, I2); | ||
operator&=(I1&, I2); | ||
operator^=(I1&, I2); | ||
operator|=(I1&, I2); | ||
For every optionally cv-qualified object type T , the following function signatures participate in overload resolution:
& operator+=(T*&, ); | ||
& operator-=(T*&, ); | ||
volatile & operator+=(T*volatile &, ); | ||
volatile & operator-=(T*volatile &, ); | ||
The following behavior-changing defect reports were applied retroactively to previously published C++ standards.
DR | Applied to | Behavior as published | Correct behavior |
---|---|---|---|
C++11 | for assignments to class type objects, the right operand could be an initializer list only when the assignment is defined by a user-defined assignment operator | removed user-defined assignment constraint | |
C++11 | E1 = {E2} was equivalent to E1 = T(E2) ( is the type of ), this introduced a C-style cast | it is equivalent to E1 = T{E2} | |
C++20 | bitwise compound assignment operators for volatile types were deprecated while being useful for some platforms | they are not deprecated | |
C++20 | compound assignment operators for volatile types were inconsistently deprecated | none of them is deprecated |
Operator precedence
Operator overloading
Common operators | ||||||
---|---|---|---|---|---|---|
a = b | ++a | +a | !a | a == b | a[...] | function call |
a(...) | ||||||
comma | ||||||
a, b | ||||||
conditional | ||||||
a ? b : c | ||||||
Special operators | ||||||
converts one type to another related type |
for Assignment operators |
Want to create or adapt books like this? Learn more about how Pressbooks supports open publishing practices.
Kenneth Leroy Busbee
Some programming languages use the idea of l-values and r-values , deriving from the typical mode of evaluation on the left and right hand side of an assignment statement. An lvalue refers to an object that persists beyond a single expression. An rvalue is a temporary value that does not persist beyond the expression that uses it. [1]
Lvalue and Rvalue refer to the left and right side of the assignment operator. The Lvalue (pronounced: L value) concept refers to the requirement that the operand on the left side of the assignment operator is modifiable, usually a variable. Rvalue concept pulls or fetches the value of the expression or operand on the right side of the assignment operator. Some examples:
The value 39 is pulled or fetched (Rvalue) and stored into the variable named age (Lvalue); destroying the value previously stored in that variable.
If the expression has a variable or named constant on the right side of the assignment operator, it would pull or fetch the value stored in the variable or constant. The value 18 is pulled or fetched from the variable named voting_age and stored into the variable named age.
If the expression is a test expression or Boolean expression, the concept is still an Rvalue one. The value in the identifier named age is pulled or fetched and used in the relational comparison of less than.
This is illegal because the identifier JACK_BENNYS_AGE does not have Lvalue properties. It is not a modifiable data object, because it is a constant.
Some uses of the Lvalue and Rvalue can be confusing in languages that support increment and decrement operators. Consider:
Postfix increment says to use my existing value then when you are done with the other operators; increment me. Thus, the first use of the oldest variable is an Rvalue context where the existing value of 55 is pulled or fetched and then assigned to the variable age; an Lvalue context. The second use of the oldest variable is an Lvalue context wherein the value of the oldest is incremented from 55 to 56.
Programming Fundamentals Copyright © 2018 by Kenneth Leroy Busbee is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License , except where otherwise noted.
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Holds a reference to an rvalue expression.
rvalue-reference-type-id : type-specifier-seq && attribute-specifier-seq opt ptr-abstract-declarator opt
Rvalue references enable you to distinguish an lvalue from an rvalue. Lvalue references and rvalue references are syntactically and semantically similar, but they follow slightly different rules. For more information about lvalues and rvalues, see Lvalues and Rvalues . For more information about lvalue references, see Lvalue Reference Declarator: & .
The following sections describe how rvalue references support the implementation of move semantics and perfect forwarding .
Rvalue references support the implementation of move semantics , which can significantly increase the performance of your applications. Move semantics enables you to write code that transfers resources (such as dynamically allocated memory) from one object to another. Move semantics works because it enables transfer of resources from temporary objects: ones that can't be referenced elsewhere in the program.
To implement move semantics, you typically provide a move constructor, and optionally a move assignment operator ( operator= ), to your class. Copy and assignment operations whose sources are rvalues then automatically take advantage of move semantics. Unlike the default copy constructor, the compiler doesn't provide a default move constructor. For more information about how to write and use a move constructor, see Move constructors and move assignment operators .
You can also overload ordinary functions and operators to take advantage of move semantics. Visual Studio 2010 introduces move semantics into the C++ Standard Library. For example, the string class implements operations that use move semantics. Consider the following example that concatenates several strings and prints the result:
Before Visual Studio 2010, each call to operator+ allocates and returns a new temporary string object (an rvalue). operator+ can't append one string to the other because it doesn't know whether the source strings are lvalues or rvalues. If the source strings are both lvalues, they might be referenced elsewhere in the program, and so must not be modified. You can modify operator+ to take rvalues by using rvalue references, which can't be referenced elsewhere in the program. With this change, operator+ can now append one string to another. The change significantly reduces the number of dynamic memory allocations that the string class must make. For more information about the string class, see basic_string Class .
Move semantics also helps when the compiler can't use Return Value Optimization (RVO) or Named Return Value Optimization (NRVO). In these cases, the compiler calls the move constructor if the type defines it.
To better understand move semantics, consider the example of inserting an element into a vector object. If the capacity of the vector object is exceeded, the vector object must reallocate enough memory for its elements, and then copy each element to another memory location to make room for the inserted element. When an insertion operation copies an element, it first creates a new element. Then it calls the copy constructor to copy the data from the previous element to the new element. Finally, it destroys the previous element. Move semantics enables you to move objects directly without having to make expensive memory allocation and copy operations.
To take advantage of move semantics in the vector example, you can write a move constructor to move data from one object to another.
For more information about the introduction of move semantics into the C++ Standard Library in Visual Studio 2010, see C++ Standard Library .
Perfect forwarding reduces the need for overloaded functions and helps avoid the forwarding problem. The forwarding problem can occur when you write a generic function that takes references as its parameters. If it passes (or forwards ) these parameters to another function, for example, if it takes a parameter of type const T& , then the called function can't modify the value of that parameter. If the generic function takes a parameter of type T& , then the function can't be called by using an rvalue (such as a temporary object or integer literal).
Ordinarily, to solve this problem, you must provide overloaded versions of the generic function that take both T& and const T& for each of its parameters. As a result, the number of overloaded functions increases exponentially with the number of parameters. Rvalue references enable you to write one version of a function that accepts arbitrary arguments. Then that function can forward them to another function as if the other function had been called directly.
Consider the following example that declares four types, W , X , Y , and Z . The constructor for each type takes a different combination of const and non- const lvalue references as its parameters.
Suppose you want to write a generic function that generates objects. The following example shows one way to write this function:
The following example shows a valid call to the factory function:
However, the following example doesn't contain a valid call to the factory function. It's because factory takes lvalue references that are modifiable as its parameters, but it's called by using rvalues:
Ordinarily, to solve this problem, you must create an overloaded version of the factory function for every combination of A& and const A& parameters. Rvalue references enable you to write one version of the factory function, as shown in the following example:
This example uses rvalue references as the parameters to the factory function. The purpose of the std::forward function is to forward the parameters of the factory function to the constructor of the template class.
The following example shows the main function that uses the revised factory function to create instances of the W , X , Y , and Z classes. The revised factory function forwards its parameters (either lvalues or rvalues) to the appropriate class constructor.
You can overload a function to take an lvalue reference and an rvalue reference.
By overloading a function to take a const lvalue reference or an rvalue reference, you can write code that distinguishes between non-modifiable objects (lvalues) and modifiable temporary values (rvalues). You can pass an object to a function that takes an rvalue reference unless the object is marked as const . The following example shows the function f , which is overloaded to take an lvalue reference and an rvalue reference. The main function calls f with both lvalues and an rvalue.
This example produces the following output:
In this example, the first call to f passes a local variable (an lvalue) as its argument. The second call to f passes a temporary object as its argument. Because the temporary object can't be referenced elsewhere in the program, the call binds to the overloaded version of f that takes an rvalue reference, which is free to modify the object.
The compiler treats a named rvalue reference as an lvalue and an unnamed rvalue reference as an rvalue.
Functions that take an rvalue reference as a parameter treat the parameter as an lvalue in the body of the function. The compiler treats a named rvalue reference as an lvalue. It's because a named object can be referenced by several parts of a program. It's dangerous to allow multiple parts of a program to modify or remove resources from that object. For example, if multiple parts of a program try to transfer resources from the same object, only the first transfer succeeds.
The following example shows the function g , which is overloaded to take an lvalue reference and an rvalue reference. The function f takes an rvalue reference as its parameter (a named rvalue reference) and returns an rvalue reference (an unnamed rvalue reference). In the call to g from f , overload resolution selects the version of g that takes an lvalue reference because the body of f treats its parameter as an lvalue. In the call to g from main , overload resolution selects the version of g that takes an rvalue reference because f returns an rvalue reference.
In the example, the main function passes an rvalue to f . The body of f treats its named parameter as an lvalue. The call from f to g binds the parameter to an lvalue reference (the first overloaded version of g ).
The C++ Standard Library std::move function enables you to convert an object to an rvalue reference to that object. You can also use the static_cast keyword to cast an lvalue to an rvalue reference, as shown in the following example:
Function templates deduce their template argument types and then use reference collapsing rules.
A function template that passes (or forwards ) its parameters to another function is a common pattern. It's important to understand how template type deduction works for function templates that take rvalue references.
If the function argument is an rvalue, the compiler deduces the argument to be an rvalue reference. For example, assume you pass an rvalue reference to an object of type X to a function template that takes type T&& as its parameter. Template argument deduction deduces T to be X , so the parameter has type X&& . If the function argument is an lvalue or const lvalue, the compiler deduces its type to be an lvalue reference or const lvalue reference of that type.
The following example declares one structure template and then specializes it for various reference types. The print_type_and_value function takes an rvalue reference as its parameter and forwards it to the appropriate specialized version of the S::print method. The main function demonstrates the various ways to call the S::print method.
To resolve each call to the print_type_and_value function, the compiler first does template argument deduction. The compiler then applies reference collapsing rules when it replaces the parameter types with the deduced template arguments. For example, passing the local variable s1 to the print_type_and_value function causes the compiler to produce the following function signature:
The compiler uses reference collapsing rules to reduce the signature:
This version of the print_type_and_value function then forwards its parameter to the correct specialized version of the S::print method.
The following table summarizes the reference collapsing rules for template argument type deduction:
Expanded type | Collapsed type |
---|---|
Template argument deduction is an important element of implementing perfect forwarding. The Perfect forwarding section describes perfect forwarding in more detail.
Rvalue references distinguish lvalues from rvalues. To improve the performance of your applications, they can eliminate the need for unnecessary memory allocations and copy operations. They also enable you to write a function that accepts arbitrary arguments. That function can forward them to another function as if the other function had been called directly.
Expressions with unary operators Lvalue reference declarator: & Lvalues and rvalues Move constructors and move assignment operators (C++) C++ Standard Library
Was this page helpful?
Join us on Facebook!
We use cookies to personalise content and ads, to provide social media features and to analyse our traffic. By using our site, you acknowledge that you have read and understand our Privacy Policy , and our Terms of Service . Your use of this site is subject to these policies and terms. | ok, got it
— Written by Triangles on June 02, 2018 • updated on February 15, 2019 • ID 63 —
A collection of personal notes and thoughts on rvalue references, their role in move semantics and how they can significantly increase the performance of your applications.
In my previous article Understanding the meaning of lvalues and rvalues in C++ I had the chance to explain to myself the logic behind rvalues . The core idea is that in C++ you will find such temporary, short-lived values that you cannot alter in any way.
Surprisingly, modern C++ (C++0x and greater) has introduced rvalue references : a new type that can bind to temporary objects, giving you the ability to modify them. Why?
Let's begin this journey with a little brush up of temporary values:
On line (1) the literal constant 666 is an rvalue: it has no specific memory address, except for some temporary register while the program is running. It needs to be stored in a lvalue ( x ) to be useful. Line (4) is similar, but here the rvalue is not hard-coded, rather it is being returned by the function getString() . However, as in line (1), the temporary object must be stored in an lvalue ( s4 ) to be meaningful.
Lines (2) and (3) seem more subtle: the compiler has to create a temporary object to hold the result of the + operator. Being a temporary one, the output is of course an rvalue that must be stored somewhere. And that's what I did by putting the results in y and s3 respectively.
The traditional C++ rules say that you are allowed to take the address of an rvalue only if you store it in a const (immutable) variable. More technically, you are allowed to bind a const lvalue to an rvalue . Consider the following example:
The first operation is wrong: it's an invalid initialization of non-const reference of type int& from an rvalue of type int . The second line is the way to go. Of course, being x a constant, you can't alter it.
C++0x has introduced a new type called rvalue reference , denoted by placing a double ampersand && after some type. Such rvalue reference lets you modify the value of a temporary object: it's like removing the const attribute in the second line above!
Let's play a bit with this new toy:
Here I create two simple strings s1 and s2 . I join them and I put the result (a temporary string, i.e. an rvalue) into std::string&& s_rref . Now s_rref is a reference to a temporary object, or an rvalue reference. There are no const around it, so I'm free to modify the temporary string to my needs. This wouldn't be possible without rvalue references and its double ampersand notation. To better distinguish it, we refer to traditional C++ references (the single-ampersand one) as lvalue references .
This might seem useless at a first glance. However rvalue references pave the way for the implementation of move semantics , a technique which can significantly increase the performance of your applications.
Move semantics is a new way of moving resources around in an optimal way by avoiding unnecessary copies of temporary objects, based on rvalue references. In my opinion, the best way to understand what move semantics is about is to build a wrapper class around a dynamic resource (i.e. a dynamically allocated pointer) and keep track of it as it moves in and out functions. Keep in mind however that move semantics does not apply only to classes!
That said, let's take a look at the following example:
It is a naive class that handles a dynamic chunk of memory: nothing fancy so far, except for the allocation part. When you choose to manage the memory yourself you should follow the so-called Rule of Three . This rule states that if your class defines one or more of the following methods it should probably explicitly define all three:
A C++ compiler will generate them by default if needed, in addition to the constructor and other functions we don't care about right now. Unfortunately the default versions are just "not enough" when your class deals with dynamic resources. Indeed, the compiler couldn't generate a constructor like the one in the example above: it doesn't know anything about the logic of our class.
Let's stick to the Rule of Three and implement the copy constructor first. As you may know, the copy constructor is used to create a new object from another existing object. For example:
How a copy constructor would look like:
Here I'm initializing a new Holder object out of the existing one passed in as other : I create a new array of the same size (1) and then I copy the actual data from other.m_data to m_data (i.e. this.m_data ) (2).
It's now time for the assignment operator, used to replace an existing object with another existing object. For example:
How an assigment operator would look like:
First of all a little protection against self-assignment (1). Then, since we are replacing the content of this class with another one, let's wipe out the current data (2). What's left is just the same code we wrote in the copy constructor. By convention a reference to this class is returned (3).
The key point of the copy constructor and the assignment operator is that they both receive a const reference to an object in input and make a copy out of it for the class they belong to. The object in input, being a constant reference, is of course left untouched.
Our class is good to go, but it lacks of some serious optimization. Consider the following function:
It returns a Holder object by value . We know that when a function returns an object by value, the compiler has to create a temporary — yet fully-fledged — object (rvalue). Now, our Holder is a heavy-weight object due to its internal memory allocation, which is a very expensive task: returning such things by value with our current class design would trigger multiple expensive memory allocations, which is rarely a great idea. How come? Consider this:
A temporary object coming out from createHolder() is passed to the copy constructor. According to our current design, the copy constructor allocates its own m_data pointer by copying the data from the temporary object. Two expensive memory allocations: a) during the creation of the temporary, b) during the actual object copy-construct operation.
The same copy procedure occurs within the assignment operator:
The code inside our assignment operator wipes the memory out and then reallocates it from scratch by copying the data from the temporary object. Yet another two expensive memory allocations: a) during the creation of the temporary, b) in the actual object assignment operator.
Too many expensive copies! We already have a fully-fledged object, the temporary and short-lived one returning from createHolder() , built for us by the compiler: it's an rvalue that will fade away with no use at the next instruction: why, during the construction/assignment stages, don't we steal — or move the allocated data inside the temporary object instead of making an expensive copy out of it?
In the old days of C++ there was no way to optimize this out: returning heavy-weight objects by value was simply a no-go. Fortunately in C++11 and greater we are allowed (and encouraged) to do this, by improving our current Holder class with move semantics. In a nutshell, we will steal existing data from temporary objects instead of making useless clones. Don't copy, just move , because moving is always cheaper.
Let's spice up our class with move semantics: the idea is to add new versions of the copy constructor and assignment operator so that they can take a temporary object in input to steal data from. To steal data means to modify the object the data belongs to: how can we modify a temporary object? By using rvalue references!
At this point we naturally follow another C++ pattern called the Rule of Five . It's an extension to the Rule of Three seen before and it states that any class for which move semantics are desirable, has to declare two additional member functions:
A typical move constructor:
It takes in input an rvalue reference to another Holder object. This is the key part: being an rvalue reference, we can modify it. So let's steal its data first (1), then set it to null (2). No deep copies here, we have just moved resources around! It's important to set the rvalue reference data to some valid state (2) to prevent it from being accidentally deleted when the temporary object dies: our Holder destructor calls delete[] m_data , remember? In general, for reasons that will become more clear in a few paragraphs, it's a good idea to always leave the objects being stolen from in some well-defined state.
The move assignment operator follows the same logic:
We steal data (2) from the other object coming in as an rvalue reference, after a cleanup of the existing resources (1). Let's not forget to put the temporary object to some valid state (3) as we did in the move constructor. Everything else is just regular assignment operator duty.
Now that we have our new methods in place, the compiler is smart enough to detect whether you are creating an object with a temporary value (rvalue) or a regular one (lvalue) and trigger the proper constructor/operator accordingly. For example:
Move semantics provide a smarter way of passing heavy-weight things around. You create your heavy-weight resource only once and then you move it where needed in a natural way. As I said before, move semantics is not only about classes. You can make use of it whenever you need to change the ownership of a resource across multiple areas of your application. However keep in mind that, unlike a pointer, you are not sharing anything: if object A steals data from object B, data in object B no longer exists, thus is no longer valid. As we know this is not a problem when dealing with temporary objects, but you can also steal from regular ones. We will see how shortly.
That's right. If you run the last snippet above you will notice how the move constructor does not get called during (1). The regular constructor is called instead: this is due to a trick called Return Value Optimization (RVO) . Modern compilers are able to detect that you are returning an object by value, and they apply a sort of return shortcut to avoid useless copies.
You can tell the compiler to bypass such optimization: for example, GCC supports the -fno-elide-constructors flag. Compile the program with such flag enabled and run it again: the amount of constructor/destructor calls will increase noticeably.
RVO is only about return values (output), not function parameters (input). There are many places where you may pass movable objects as input parameters, which would make the move constructor and the move assignment operator come into play, if implemented. The most important one: the Standard Library. During the upgrade to C++11 all the algorithms and containers in there were extended to support move semantics. So if you use the Standard Library with classes that follow the Rule of Five you will gain an important optimization boost.
Yes you can, with the utility function std::move from the Standard Library. It is used to convert an lvalue into an rvalue. Say we want to steal from an lvalue:
This will not work: since h2 receives an lvalue in input, the copy constructor is being triggered. We need to force the move constructor on h2 in order to make it steal from h1 , so:
Here std::move has converted the lvalue h1 into an rvalue: the compiler sees such rvalue in input and then triggers the move constructor on h2 . The object h2 will steal data from h1 during its construction stage.
Mind that at this point h1 is a hollow object. However, we did a good thing when in our move constructor we set the stolen object's data to a valid state ( other.m_data = nullptr , remember?). Now you may want to reuse h1 , test it in some way or let it go out of scope without causing nasty crashes.
This article is way too long and I've only scratched the surface of move semantics. What follows is a quick list of additional concepts I will further investigate in the future.
Resource Acquisition Is Initialization (RAII) is a C++ technique where you wrap a class around a resource (file, socket, database connection, allocated memory, ...). The resource is initialized in the class constructor and cleaned up in the class destructor. This way you are sure to avoid resource leaks. More information: here .
The C++11 keyword noexcept means "this function will never throw exceptions". It is used to optimize things out. Some people say that move constructors and move assignment operators should never throw. Rationale: you should not allocate memory or call other code in there. You should only copy data and set the other object to null, i.e. non-throwing operations. More information: here , here .
All the constructors/assignment operators in the Holder class are full of duplicate code, which is not so great. Moreover, if the allocation throws an exception in the copy assignment operator the source object might be left in a bad state. The copy-and-swap idiom fixes both issues, at the cost of adding a new method to the class. More information: here , here .
This technique allows you to move your data across multiple template and non-template functions without wrong type conversions (i.e. perfectly). More information: here , here .
Stack Overflow - When is an rvalue evaluated? ( link ) Mikw's C++11 blog - Lesso #5: Move Semantics ( link ) Artima - A Brief Introduction to Rvalue References ( link ) Stack Overflow - C++11 rvalues and move semantics confusion (return statement) ( link ) Cpp-patterns - The rule of five ( link ) open-std.org - A Brief Introduction to Rvalue References ( link ) Microsoft - Rvalue Reference Declarator: && ( link ) Wikipedia - Rule of three (C++ programming) ( link ) Stack Overflow - What are all the member-functions created by compiler for a class? Does that happen all the time? ( link ) cplusplus.com - Copy constructors, assignment operators, and exception safe assignment ( link ) Stack Overflow - What is the copy-and-swap idiom? ( link ) Wikipedia - Assignment operator (C++) ( link ) Stack Overflow - When the move constructor is actually called if we have (N)RVO? ( link ) cppreference.com - The rule of three/five/zero ( link ) cprogramming.com - Move semantics and rvalue references in C++11 ( link ) Stack Overflow - What is std::move(), and when should it be used? ( link )
(C++20) | ||||
(C++20) | ||||
(C++11) | ||||
(C++11) | ||||
(C++11) | ||||
(C++17) | ||||
General topics | ||||
(C++11) |
- |
-expression |
- block |
(C++11) | ||||
(C++11) | ||||
(C++11) |
/ | ||||
(C++11) |
(C++11) | ||||
Expressions | ||||
expression |
pointer |
specifier |
(C++11) | ||||
(C++11) |
(C++11) | ||||
(C++11) |
(C++11) | ||||
(C++11) |
General | ||||
(lvalue, rvalue, xvalue) | ||||
(sequence points) | ||||
(C++11) | ||||
Literals | ||||
including | ||||
(C++11) | ||||
(C++11) | ||||
Operators | ||||
: , , , , , , , , , , | ||||
: , , , | ||||
: , , , , , , , , , , , , | ||||
: , , | ||||
: , , , , , , (C++20) | ||||
: , , , , , , | ||||
: , , | ||||
(C++20) | ||||
(C++17) | ||||
(C++11) | ||||
(C++11) | ||||
Conversions | ||||
, | ||||
Assignment operators modify the value of the object.
Operator name | Syntax | Prototype examples (for class T) | ||
---|---|---|---|---|
Inside class definition | Outside class definition | |||
simple assignment | Yes | T& T::operator =(const T2& b); | ||
addition assignment | Yes | T& T::operator +=(const T2& b); | T& operator +=(T& a, const T2& b); | |
subtraction assignment | Yes | T& T::operator -=(const T2& b); | T& operator -=(T& a, const T2& b); | |
multiplication assignment | Yes | T& T::operator *=(const T2& b); | T& operator *=(T& a, const T2& b); | |
division assignment | Yes | T& T::operator /=(const T2& b); | T& operator /=(T& a, const T2& b); | |
modulo assignment | Yes | T& T::operator %=(const T2& b); | T& operator %=(T& a, const T2& b); | |
bitwise AND assignment | Yes | T& T::operator &=(const T2& b); | T& operator &=(T& a, const T2& b); | |
bitwise OR assignment | Yes | T& T::operator |=(const T2& b); | T& operator |=(T& a, const T2& b); | |
bitwise XOR assignment | Yes | T& T::operator ^=(const T2& b); | T& operator ^=(T& a, const T2& b); | |
bitwise left shift assignment | Yes | T& T::operator <<=(const T2& b); | T& operator <<=(T& a, const T2& b); | |
bitwise right shift assignment | Yes | T& T::operator >>=(const T2& b); | T& operator >>=(T& a, const T2& b); | |
this, and most also return *this so that the user-defined operators can be used in the same manner as the built-ins. However, in a user-defined operator overload, any type can be used as return type (including void). can be any type including |
copy assignment operator replaces the contents of the object a with a copy of the contents of b ( b is not modified). For class types, this is a special member function, described in copy assignment operator .
move assignment operator replaces the contents of the object a with the contents of b , avoiding copying if possible ( b may be modified). For class types, this is a special member function, described in move assignment operator . (since C++11)
For non-class types, copy and move assignment are indistinguishable and are referred to as direct assignment .
compound assignment operators replace the contents of the object a with the result of a binary operation between the previous value of a and the value of b .
The direct assignment expressions have the form
lhs rhs | (1) | ||||||||
lhs = {} | (2) | (since C++11) | |||||||
lhs = {rhs} | (3) | (since C++11) | |||||||
For the built-in operator, lhs may have any non-const scalar type and rhs must be implicitly convertible to the type of lhs .
The direct assignment operator expects a modifiable lvalue as its left operand and an rvalue expression or a braced-init-list (since C++11) as its right operand, and returns an lvalue identifying the left operand after modification.
For non-class types, the right operand is first implicitly converted to the cv-unqualified type of the left operand, and then its value is copied into the object identified by left operand.
When the left operand has reference type, the assignment operator modifies the referred-to object.
If the left and the right operands identify overlapping objects, the behavior is undefined (unless the overlap is exact and the type is the same)
If the right operand is a has scalar type, {} is equivalent to E1 = T{}, where is the type of . {E2} is equivalent to E1 = T{E2}, where is the type of . has class type, the syntax E1 = {args...} generates a call to the assignment operator with the as the argument, which then selects the appropriate assignment operator following the rules of . Note that, if a non-template assignment operator from some non-class type is available, it is preferred over the copy/move assignment in because to non-class is an , which outranks the user-defined conversion from to a class type. | (since C++11) |
In overload resolution against user-defined operators , for every type T , the following function signatures participate in overload resolution:
& operator=(T*&, T*); | ||
volatile & operator=(T*volatile &, T*); | ||
For every enumeration or pointer to member type T , optionally volatile-qualified, the following function signature participates in overload resolution:
operator=(T&, T ); | ||
For every pair A1 and A2, where A1 is an arithmetic type (optionally volatile-qualified) and A2 is a promoted arithmetic type, the following function signature participates in overload resolution:
operator=(A1&, A2); | ||
The compound assignment expressions have the form
lhs op rhs | (1) | ||||||||
lhs op {} | (2) | (since C++11) | |||||||
lhs op {rhs} | (3) | (since C++11) | |||||||
op | - | one of *=, /= %=, += -=, <<=, >>=, &=, ^=, |= |
lhs | - | for the built-in operator, lhs may have any arithmetic type, except when op is += or -=, which also accept pointer types with the same restrictions as + and - |
rhs | - | for the built-in operator, rhs must be implicitly convertible to lhs |
The behavior of every builtin compound-assignment expression E1 op = E2 (where E1 is a modifiable lvalue expression and E2 is an rvalue expression or a braced-init-list (since C++11) ) is exactly the same as the behavior of the expression E1 = E1 op E2 , except that the expression E1 is evaluated only once and that it behaves as a single operation with respect to indeterminately-sequenced function calls (e.g. in f ( a + = b, g ( ) ) , the += is either not started at all or is completed as seen from inside g ( ) ).
In overload resolution against user-defined operators , for every pair A1 and A2, where A1 is an arithmetic type (optionally volatile-qualified) and A2 is a promoted arithmetic type, the following function signatures participate in overload resolution:
operator*=(A1&, A2); | ||
operator/=(A1&, A2); | ||
operator+=(A1&, A2); | ||
operator-=(A1&, A2); | ||
For every pair I1 and I2, where I1 is an integral type (optionally volatile-qualified) and I2 is a promoted integral type, the following function signatures participate in overload resolution:
operator%=(I1&, I2); | ||
operator<<=(I1&, I2); | ||
operator>>=(I1&, I2); | ||
operator&=(I1&, I2); | ||
operator^=(I1&, I2); | ||
operator|=(I1&, I2); | ||
For every optionally cv-qualified object type T , the following function signatures participate in overload resolution:
& operator+=(T*&, ); | ||
& operator-=(T*&, ); | ||
volatile & operator+=(T*volatile &, ); | ||
volatile & operator-=(T*volatile &, ); | ||
Operator precedence
Operator overloading
Common operators | ||||||
---|---|---|---|---|---|---|
a = b | ++a | +a | !a | a == b | a[b] | a(...) |
Special operators | ||||||
converts one type to another related type |
The terms lvalue and rvalue are not something one runs into often in C/C++ programming, but when one does, it's usually not immediately clear what they mean. The most common place to run into these terms are in compiler error & warning messages. For example, compiling the following with gcc :
True, this code is somewhat perverse and not something you'd write, but the error message mentions lvalue , which is not a term one usually finds in C/C++ tutorials. Another example is compiling this code with g++ :
Now the error is:
Here again, the error mentions some mysterious rvalue . So what do lvalue and rvalue mean in C and C++? This is what I intend to explore in this article.
This section presents an intentionally simplified definition of lvalues and rvalues . The rest of the article will elaborate on this definition.
An lvalue ( locator value ) represents an object that occupies some identifiable location in memory (i.e. has an address).
rvalues are defined by exclusion, by saying that every expression is either an lvalue or an rvalue . Therefore, from the above definition of lvalue , an rvalue is an expression that does not represent an object occupying some identifiable location in memory.
The terms as defined above may appear vague, which is why it's important to see some simple examples right away.
Let's assume we have an integer variable defined and assigned to:
An assignment expects an lvalue as its left operand, and var is an lvalue, because it is an object with an identifiable memory location. On the other hand, the following are invalid:
Neither the constant 4 , nor the expression var + 1 are lvalues (which makes them rvalues). They're not lvalues because both are temporary results of expressions, which don't have an identifiable memory location (i.e. they can just reside in some temporary register for the duration of the computation). Therefore, assigning to them makes no semantic sense - there's nowhere to assign to.
So it should now be clear what the error message in the first code snippet means. foo returns a temporary value which is an rvalue. Attempting to assign to it is an error, so when seeing foo() = 2; the compiler complains that it expected to see an lvalue on the left-hand-side of the assignment statement.
Not all assignments to results of function calls are invalid, however. For example, C++ references make this possible:
Here foo returns a reference, which is an lvalue , so it can be assigned to. Actually, the ability of C++ to return lvalues from functions is important for implementing some overloaded operators. One common example is overloading the brackets operator [] in classes that implement some kind of lookup access. std::map does this:
The assignment mymap[10] works because the non-const overload of std::map::operator[] returns a reference that can be assigned to.
Initially when lvalues were defined for C, it literally meant "values suitable for left-hand-side of assignment". Later, however, when ISO C added the const keyword, this definition had to be refined. After all:
So a further refinement had to be added. Not all lvalues can be assigned to. Those that can are called modifiable lvalues . Formally, the C99 standard defines modifiable lvalues as:
[...] an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a const-qualified type.
Generally speaking, language constructs operating on object values require rvalues as arguments. For example, the binary addition operator '+' takes two rvalues as arguments and returns an rvalue:
As we've seen earlier, a and b are both lvalues. Therefore, in the third line, they undergo an implicit lvalue-to-rvalue conversion . All lvalues that aren't arrays, functions or of incomplete types can be converted thus to rvalues.
What about the other direction? Can rvalues be converted to lvalues? Of course not! This would violate the very nature of an lvalue according to its definition [1] .
This doesn't mean that lvalues can't be produced from rvalues by more explicit means. For example, the unary '*' (dereference) operator takes an rvalue argument but produces an lvalue as a result. Consider this valid code:
Conversely, the unary address-of operator '&' takes an lvalue argument and produces an rvalue:
The ampersand plays another role in C++ - it allows to define reference types. These are called "lvalue references". Non-const lvalue references cannot be assigned rvalues, since that would require an invalid rvalue-to-lvalue conversion:
Constant lvalue references can be assigned rvalues. Since they're constant, the value can't be modified through the reference and hence there's no problem of modifying an rvalue. This makes possible the very common C++ idiom of accepting values by constant references into functions, which avoids unnecessary copying and construction of temporary objects.
If we read carefully the portion of the C++ standard discussing lvalue-to-rvalue conversions [2] , we notice it says:
An lvalue (3.10) of a non-function, non-array type T can be converted to an rvalue. [...] If T is a non-class type, the type of the rvalue is the cv-unqualified version of T. Otherwise, the type of the rvalue is T.
What is this "cv-unqualified" thing? CV-qualifier is a term used to describe const and volatile type qualifiers.
From section 3.9.3:
Each type which is a cv-unqualified complete or incomplete object type or is void (3.9) has three corresponding cv-qualified versions of its type: a const-qualified version, a volatile-qualified version, and a const-volatile-qualified version. [...] The cv-qualified or cv-unqualified versions of a type are distinct types; however, they shall have the same representation and alignment requirements (3.9)
But what has this got to do with rvalues? Well, in C, rvalues never have cv-qualified types. Only lvalues do. In C++, on the other hand, class rvalues can have cv-qualified types, but built-in types (like int ) can't. Consider this example:
The second call in main actually calls the foo () const method of A , because the type returned by cbar is const A , which is distinct from A . This is exactly what's meant by the last sentence in the quote mentioned earlier. Note also that the return value from cbar is an rvalue. So this is an example of a cv-qualified rvalue in action.
Rvalue references and the related concept of move semantics is one of the most powerful new features the C++11 standard introduces to the language. A full discussion of the feature is way beyond the scope of this humble article [3] , but I still want to provide a simple example, because I think it's a good place to demonstrate how an understanding of what lvalues and rvalues are aids our ability to reason about non-trivial language concepts.
I've just spent a good part of this article explaining that one of the main differences between lvalues and rvalues is that lvalues can be modified, and rvalues can't. Well, C++11 adds a crucial twist to this distinction, by allowing us to have references to rvalues and thus modify them, in some special circumstances.
As an example, consider a simplistic implementation of a dynamic "integer vector". I'm showing just the relevant methods here:
So, we have the usual constructor, destructor, copy constructor and copy assignment operator [4] defined, all using a logging function to let us know when they're actually called.
Let's run some simple code, which copies the contents of v1 into v2 :
What this prints is:
Makes sense - this faithfully represents what's going on inside operator= . But suppose that we want to assign some rvalue to v2 :
Although here I just assign a freshly constructed vector, it's just a demonstration of a more general case where some temporary rvalue is being built and then assigned to v2 (this can happen for some function returning a vector, for example). What gets printed now is this:
Ouch, this looks like a lot of work. In particular, it has one extra pair of constructor/destructor calls to create and then destroy the temporary object. And this is a shame, because inside the copy assignment operator, another temporary copy is being created and destroyed. That's extra work, for nothing.
Well, no more. C++11 gives us rvalue references with which we can implement "move semantics", and in particular a "move assignment operator" [5] . Let's add another operator= to Intvec :
The && syntax is the new rvalue reference . It does exactly what it sounds it does - gives us a reference to an rvalue, which is going to be destroyed after the call. We can use this fact to just "steal" the internals of the rvalue - it won't need them anyway! This prints:
What happens here is that our new move assignment operator is invoked since an rvalue gets assigned to v2 . The constructor and destructor calls are still needed for the temporary object that's created by Intvec(33) , but another temporary inside the assignment operator is no longer needed. The operator simply switches the rvalue's internal buffer with its own, arranging it so the rvalue's destructor will release our object's own buffer, which is no longer used. Neat.
I'll just mention once again that this example is only the tip of the iceberg on move semantics and rvalue references. As you can probably guess, it's a complex subject with a lot of special cases and gotchas to consider. My point here was to demonstrate a very interesting application of the difference between lvalues and rvalues in C++. The compiler obviously knows when some entity is an rvalue, and can arrange to invoke the correct constructor at compile time.
One can write a lot of C++ code without being concerned with the issue of rvalues vs. lvalues, dismissing them as weird compiler jargon in certain error messages. However, as this article aimed to show, getting a better grasp of this topic can aid in a deeper understanding of certain C++ code constructs, and make parts of the C++ spec and discussions between language experts more intelligible.
Also, in the new C++ spec this topic becomes even more important, because C++11's introduction of rvalue references and move semantics. To really grok this new feature of the language, a solid understanding of what rvalues and lvalues are becomes crucial.
rvalues can be assigned to lvalues explicitly. The lack of implicit conversion means that rvalues cannot be used in places where lvalues are expected. |
That's section 4.1 in the new C++11 standard draft. |
You can find a lot of material on this topic by simply googling "rvalue references". Some resources I personally found useful: , , and . |
This a canonical implementation of a copy assignment operator, from the point of view of exception safety. By using the copy constructor and then the non-throwing , it makes sure that no intermediate state with uninitialized memory can arise if exceptions are thrown. |
So now you know why I was keeping referring to my as "copy assignment operator". In C++11, the distinction becomes important. |
For comments, please send me an email .
This is an attempt to explain new && reference present in latest versions of compilers as part of implementing the new C++ 11 standard. Such as those shipping with Visual studio 10-11-12 and gcc 4.3-4, or beautiful fast( equally if not more) open-source alternative to gcc Clang.
Why you should start using it ? In short : Nontrivial performance gain. For example inserts to std::vector (or in fact any array creation) will not cost huge amount of allocs/copies anymore .
But before we go to detail of new c++ 11 "Move semantics" we need to understand the core of problem. We need to understand why performance problem of c++ language with = assign operation often resulting to useless alloc/copy exists.
We live in the world where we need to work with a lot of data. We need to store and process it in an effective manner. Problem with c and c++ is that we got used to do it ineffectively.
The real price for copying via = was small since pointers are just numbers (containing memory addresses). But with objects the story is different.
Now I will try to keep this short but it is important to really understand why && operator was born.
In C++ working with references become prevalent since they are safer alternative to pointers and very importantly new language features such as automatic call of constructors or destructors (automatic cleanup) or operators worked only with them.
Now make no mistake References are internally still just pointers but this time made little bit safer and automatically dereferenced. Period. How are they made safer? Well. They can never contain invalid data since the only assignment to them is allowed during their declaration and only to existing statically declared data. But whenever you pass reference to function in fact it is still just pointer that is internally pushed on stack.
C++ wanted to make our life easier by doing routine pointer de/referencing for us (and hiding this pointer wizardry from us). Illusion of passing or working with objects instead of pointers to them was so perfect that many lost sense what is actually reference and what is object.
Exactly this ambiguity had unfortunate side effect that led us into believing that this
is just is safer alternative to this
We just got rid of unsafe pointers in favor of safer references like everywhere else.But more importantly this has given us automatic call of constructors destructor and operators. Right? Wrong. There is no such thing as array of references in C++. No pointer magic behind the scene is going on this time like it is within functions. So what we actually created is algorithmically very bad decision. We created an array of objects stored by value. And no. Removing * from declaration doesn't automatically make pointer variable reference. From performance point of view there is really no alternative to array of pointers . With big objects containing statically declared structured data there is big performance difference when creating sorting searching and mainly reallocating 100mb array of values. Then it is with few bytes of pointers to them. It pretty much kills the possibility to work within processor cache with as much data as possible. So we sort/search/reallocate etc on the speed of main memory order of magnitude slower instead of cpu cache which we now uselessly pollute with unimportant object data. With objects containing big dynamic data situation is better when sorting etc. But still bad when assigned to array. Since then we need to allocate and copy large chunks of dynamic mem plus rest of object. But I would say mentioned reallocation is worst consequence of storing object by values.
So every time you think of "nah lets put vector<large-object> there". Your memory requirements will be twice of what they need to be and your performance abysmal due to fact that whole allocated mem will be uselessly moved around after each realloc. One would think that this is just price we indeed agreed to pay for generic approaches and laziness. But by my opinion this is the price for storing objects instead of pointers for the oo reasons (automatic constructors destructors operators) mentioned above.
But back to the topic. As we remember assign = "actually" copies data. And this leads us to another big performance problem with arrays.
How to efficiently build array of large objects. Suppose you wanna build a city full of skyscrapers and you obviously (due to large scale) can't afford any waste of time or resources.
Now think of city as an array analogy. So what you actually do is you "create" building inside city
But thanks to our habit of using = operator to store object pointers to array in good old C. Its only natural that we attempt to use the same "create and assign" paradigm with references too; In other words. We simply got used to it and what's worse every c++ book teaches as to do it this ineffective way.
It manifests with arrays so strongly because of sheer number of ineffective assignments. In case of our benchmark its 500 assignments but if you do any reference assignment that can be handled by moving (as explained later) and not copying in loop with 500 iterations you have basically the same problem.
Yes there are but they are nor intuitive or obvious and are hack like in nature. Now if C++ did allow us to invoke specialized constructor and create objects using already allocated space inside array(that was allocated just once for all elements by one efficient alloc). Then this could saved zillion of allocs/copies most of us usually do by assigning new objects via = ;
Still. There are some ways to do it. You can for example move all your initialization code to method and invoke it on array element explicitly.
Hurray. The problem introduced by using = is gone.
That means now it doesn't matter if object have large statically declared array or structure. No copy no problem. Most importantly the wasted cpu on moving mostly useless empty bytes is gone. Also positive is the fact that reading and writing such a large chunk of memory which pretty much flushed all cpu caches bringing down performance of the rest of the application is gone too.
That's all nice and great. But chances that people will stop putting code to constructors in favor of some standard create method are pretty slim.
Using constructors to create everything is paradigm that we got so used to and love. Exactly as we got trained by misleading books and used = operator for storing data to arrays.
Still. There is way to do it with constructor via little known variant of operator new Its called " placement new " where you construct object on existing memory with this pointer provided by you. But now we are entering very weird confusing and little bit dangerous territory due to word new flying around statically declared array. New that doesn't allocate anything. New that is here just as a kludge to invoke constructor. Why dangerous? The moment you overload something as fundamental as allocator New brace yourself for all kind of troubles http://www.drdobbs.com/article/print?articleID=184401369&dept_url=/cpp/
Most importantly when vector goes out of scope no destructors are automatically called. it can be done manually but it reintroduces source of bugs.
Is automatic cleanup of objects stored by pointers really that impossible in current c++?
Now consider following weird but perfectly working example. Remember stack is defaultly limited (unless you change it in linker settings) resource. So take this as purely academic example that array of pointers that automatically calls destructors when going out of scope is possible.
The moment array of objects stored by pointers goes out of scope they are automatically deallocated without any manual destructor invocation; How come this works ? What is going on? = A() is internally the same as = new A(); the same constructor is invoked. Except for first stack is used by allocator and heap for second. both return pointers. references are pointers(just meeting certain criteria to deserve label reference) as we remember right ?
Yes for heap pointers (created by new) there is kludge of wrapping all pointers to objects simulating pointers via operator overloading aka(smart pointers) in std::shared_ptr and alike. and store just those. So if you dont mind wrapping all your variables to functionality hiding impossible to debug macros/templates then this is very good solution for you.
But I strongly believe that simple things shall not be encrypted or hidden from sight nor does every variable. Programmer must be aware of what is going on as much as possible like it was in C. without having template and macro expander build in his head. And if you ask Linus to obfuscate every pointer to template wrapper he would most probably kill you. I remember strong backlash against excessive macro usage in C. And there was rational reason for that.
That reason was "complexity and hiding code logic is source of bugs".
So this internally can be optimized (via explicit optimization switch) to something like
This would fix dynamic and static(object mem) waste = zero alloc/copy since elements are created just once in already allocated memory as it always should had been for performance reasons. Why is static(non-dynamic) mem waste equally if not more important? Majority of objects are small and selfcontained. And when you look at benchmark bellow storing object containing statically declared array took 5662 ms yet storing object containing dynamic array of the same size took 1372 ms. Also. After such change to compiler all old code using big objects would start pretty much flying at completely different speeds just by recompiling.
Because I am curious person I am attempting to implement and test it in clang fantastic open source c++compiler as a optimization switch or pragma. Should you wanna lend a hand I will be more than thankful http://clang-developers.42468.n3.nabble.com/Proposed-C-optimization-with-big-speed-gains-with-big-objects-tt4026886.html But let's focus on latest C++ solution to it (unfortunately only for heap mem in your objects and with a lot of code changes)
Move semantics enables you to write code that transfers dynamically allocated memory from one object to another. Move semantics works because it enables this memory to be transferred from temporary objects(by copying just pointers) that cannot be referenced elsewhere in the program. Unfortunately large statically declared (contained-within object) arrays/structs/members data must still be uselessly copied since as mentioned they are contained within temp object themselves that is about to be destroyed.
To implement move semantics, you typically provide a move constructor, and optionally a move assignment operator= to your class. Copy and assignment operations whose sources are (temp objects or data that can't change) then automatically take advantage of move semantics. Unlike the default copy constructor, the compiler does not provide a default move constructor. You can also overload ordinary functions and operators to take advantage of move semantics. Visual C++ 2010 introduces move semantics into the Standard Template Library (STL). For example, the string class implements operations that perform move semantics. Consider the following example that concatenates several strings.
Before && references existed, each call to operator+ allocated and returned a new temp object. operator+ couldn't append one string to the other because it didn't know whether content of the source can be tampered with (temps) or not (variables). If the source strings are both variables, they might be referenced elsewhere in the program and therefore must not be modified. But now thanks to && reference we now know that temp (which cannot be referenced elsewhere in the program) was passed in. Therefore, operator+ can now safely append one string to another. This can significantly reduce the number of dynamic memory allocations that the string class must perform.
Move semantics also helps when the compiler cannot use Return Value Optimization (RVO) or Named Return Value Optimization (NRVO). In these cases, the compiler calls the move constructor if the type defines it.
As an another example consider the example of inserting an element into a vector object. If the capacity of the vector object is exceeded, the vector object must reallocate memory for its elements and then copy each element to another memory location to make room for the inserted element. When an insertion operation copies an element, it creates a new element, calls the copy constructor to copy the data from the previous element to the new element, and then destroys the previous element. Move semantics enables you to move objects directly without having to perform expensive memory allocation and copy operations.
So. To take advantage of move semantics to allow efficient insert of your objects in the std::vector, you must write a move constructor to allow moving of data from one object to another.
For complete compilable example copy benchmark code bellow to dev env of your choice.
No for those who thing everything was clear and obvious in previous example. Dont' let the eyes fool you.
Skyscraper && in is not actually of type && anymore. The moment it enters function its & again. So if you wana forward && to another function you need to cast it to && again (in stl via std::move ). Why c++ decided to do this behind your back hidden functionality ? Well I am being told that it's security precaution. That any && having name is in risk of being referenced somewhere else in code and thus it's not deemed safe for keeping "moveable" status. Seems like some unfinished c++ business to me since I can't imagine referencing local variable outside of this proc. Also there is little know feature of ref-specifiers where you can restrict operator/methods to accept just temps or just variables.
Now, you can't anymore say
Unfortunately this doesn't yet seem to be supported in Visual Studio 2012 RC1 that I am using right now.
So to summarize. Unless you use contained big statically declared (stored within object) structures/arrays/members the result is significant speedup of your existing code. Tho see how much you can speedup your existing code (well... actually you stop slowing it down)I created simple practical && example along with benchmark results. But if you do more than just stupid memset in your constructors/destructors speedups will be significantly higher.
Store objects containing array themself took 5662 ms // even with && this is still problem sort objects containing array themself took 17660 ms //this is why you should not store objects store objects containing dynamic array by copying took 1372 ms store objects containing dynamic array by moving (c++ 11) took 500 ms store just pointers to objects took 484 ms sort just pointers to objects took 0 ms , benchmark code .
To have an idea how bad the usual careless assign = is. I created example storing 500 large objects to array via different methods and measured time it takes. Texture represents standard large object we work in c++ on daily basis = just large chunk of data and its size plus mandatory operator = to be able to be stored by value. Now it's stripped to bare minimum on purpose(no chaining consts etc) . with only simple types so you can focus only on those two operators. And sort has < reversed to simulate worst case scenario.
Now the more observable of you would probably would start arguing...
"This is nothing new I could do this data "moving" (or just passing data along between objects without copying) the same way in current standard c++ operator = & so why do I need new && operator ?
Yes you can and No you can't. If you did moving in operator = & like this. Imagine what would happen
If we moved = copied just pointers to data in standard operator = &
Then whenewer b changes c changes too; And this was not intended
Unfortunately & up to c++ 11 could not distingush if passed data can change so moving was not possible in current c++ standard for the reasons explained in c=b example. the new && in turn can distingush that data which cant change was passed in and thus its safe just point to its data and skip copying. So to summarize. in new c++ 11 standard you are now supposed to keep two sets of operators and constructors
Why it's called rvalue reference && , now the whole article i was deliberately was not using termins like rvalues(cant change) and lvalues(can change) since they are not what their names imply. .
lvalue should had been named something like "variable" rvalue should had been named something like "temp"
They are just technical grammar remnants confusing people. the were born from how C grammar in lex and yacc was described eg on what side of "that particular grammar rule they vere located = left or right" BUT that particular rule can be part of larger expression and lvalue is sudenly rvalue. Or let me explain it this way. Anything not having name is rvalue otherwise it's lvalue
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
IMAGES
COMMENTS
The move assignment operator is called whenever it is selected by overload resolution, e.g. when an object appears on the left-hand side of an assignment expression, where the right-hand side is an rvalue of the same or implicitly convertible type.. Move assignment operators typically "steal" the resources held by the argument (e.g. pointers to dynamically-allocated objects, file descriptors ...
This topic describes how to write a move constructor and a move assignment operator for a C++ class. A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying. For more information about move semantics, see Rvalue Reference Declarator: &&. This topic builds upon the following C++ class ...
In C++03, an expression is either an rvalue or an lvalue. In C++11, an expression can be an: rvalue lvalue xvalue glvalue prvalue Two categories have become five categories. What are these new ... For example, the built-in assignment operators expect that the left operand is an lvalue and that the right operand is a prvalue and yield an lvalue ...
It's now "obvious" that the correct way to write a copy-and-swap assignment is: T& operator=(T x) // x is a copy of the source; hard work already done. {. swap(*this, x); // trade our resources for x's. return *this; // our (old) resources get destroyed with x. } It is said that we should write assignement operator with argument by value ...
probe& operator =(probe tmp) { swap(tmp); return *this; } Now enter C++11 with rvalue references and move semantics. You decided to add a move assignment operator: probe& operator =(probe&&); Now calling the assignment operator on a temporary creates an ambiguity because both overloads are viable and none is preferred over the other.
An rvalue can't be used as the left-hand operand of the built-in assignment or compound assignment operators. An rvalue may be used to initialize a const lvalue reference , in which case the lifetime of the temporary object identified by the rvalue is extended until the scope of the reference ends.
A common use case is the "move constructor," invoked when an object is being copied from a temporary that's about to expire (a rvalue). An example is foo = bar + baz; where bar + baz is a temporary on the right-hand side of an assignment statement.. Before rvalue references, objects that made deep copies of their contents had to construct their data in one place, then copy it all to ...
Lvalue and rvalue expressions. An lvalue (pronounced "ell-value", short for "left value" or "locator value", and sometimes written as "l-value") is an expression that evaluates to an identifiable object or function (or bit-field).. The term "identity" is used by the C++ standard, but is not well-defined. An entity (such as an object or function) that has an identity can be ...
The direct assignment operator expects a modifiable lvalue as its left operand and an rvalue expression or a braced-init-list (since C++11) as its right operand, and returns an lvalue identifying the left operand after modification. The result is a bit-field if the left operand is a bit-field.
An rvalue is a temporary value that does not persist beyond the expression that uses it. [1] Discussion. Lvalue and Rvalue refer to the left and right side of the assignment operator. The Lvalue (pronounced: L value) concept refers to the requirement that the operand on the left side of the assignment operator is modifiable, usually a variable.
22.2 — R-value references. Alex May 15, 2024. In chapter 12, we introduced the concept of value categories ( 12.2 -- Value categories (lvalues and rvalues) ), which is a property of expressions that helps determine whether an expression resolves to a value, function, or object. We also introduced l-values and r-values so that we could discuss ...
Holds a reference to an rvalue expression. ... To implement move semantics, you typically provide a move constructor, and optionally a move assignment operator (operator=), to your class. Copy and assignment operations whose sources are rvalues then automatically take advantage of move semantics. Unlike the default copy constructor, the ...
The same copy procedure occurs within the assignment operator: int main() { Holder h = createHolder(1000); // Copy constructor h = createHolder(500); // Assignment operator } The code inside our assignment operator wipes the memory out and then reallocates it from scratch by copying the data from the temporary object. Yet another two expensive ...
for assignments to class type objects, the right operand could be an initializer list only when the assignment is defined by a user-defined assignment operator. removed user-defined assignment constraint. CWG 1538. C++11. E1 ={E2} was equivalent to E1 = T(E2) ( T is the type of E1 ), this introduced a C-style cast. it is equivalent to E1 = T{E2}
For the built-in operator, lhs may have any non-const scalar type and rhs must be implicitly convertible to the type of lhs. The direct assignment operator expects a modifiable lvalue as its left operand and an rvalue expression or a braced-init-list (since C++11) as its right operand, and returns an lvalue identifying the left operand after modification.
What happens here is that our new move assignment operator is invoked since an rvalue gets assigned to v2. The constructor and destructor calls are still needed for the temporary object that's created by Intvec(33), but another temporary inside the assignment operator is no longer needed. The operator simply switches the rvalue's internal ...
rvalue should had been named something like "temp" So whenever you read text using lvalue and rvalue just replace those two and sudenly text will make sense . ... The sort operation shows the value of implementing the move assignment operator and the move constructor, and while your "benchmark" shows a performance improvement, it doesn't really ...
Triviality of eligible copy assignment operators determines whether the class is a trivially copyable type. [] NoteIf both copy and move assignment operators are provided, overload resolution selects the move assignment if the argument is an rvalue (either a prvalue such as a nameless temporary or an xvalue such as the result of std::move), and selects the copy assignment if the argument is an ...
21.12 — Overloading the assignment operator. Alex July 22, 2024. The copy assignment operator (operator=) is used to copy values from one object to another already existing object. As of C++11, C++ also supports "Move assignment". We discuss move assignment in lesson 22.3 -- Move constructors and move assignment .
Passing the rvalue rtByValue() to a function that expects an lvalue reference doesn't work because this would require the lvalue reference argument to be initialized from an rvalue. §8.5.3/5 describes how lvalue references can be initialized - I won't quote it in full, but it basically says that an lvalue reference can be initialized. either from another lvalue reference
This may be an abuse of the assignment operator to get an lvalue from an rvalue but it is unlikely to happen by accident. Thus, I wouldn't go out of my way and make this impossible by restricting the assignment operator to be applicable to lvalues only. Of course, returning an rvalue from an assignment to an rvalue would also prevent this.