CopperSpice API  1.7.2
References and Move Semantics

C++11 introduced a new reference data type and support for move semantics. Applications which leverage both of these will generally be more efficient and faster. The CopperSpice libraries are being redesigned to use move semantics as classes are enhanced and improved.

The following sections provide a brief overview of references and move semantics.

Expressions

To understand the new reference data type, a definition of an expression is required. An expression consists of a sequence of operators and their operands which describes a computation. The following code is a typical expression.

x = 2 + 3

The operands x, 2, and 3 are the components of interest in this line of code. Each of these components is an expression and the entire line of code is also an expression. Every expression has a Data Type and belongs to a Value Category.

Most programmers are very familiar with the idea of data types such as int, double, bool, QString, QMap, etc. The idea of a value category is not unique to C++ however it is not often considered when developing applications.

Value Category

The two fundamental value categories in C++ are lvalue and rvalue. Every expression is either an lvalue or an rvalue, it can not be both. It is extremely important to know which value category an expression belongs to in order to use move semantics correctly.

The determination of an expression being an lvalue or an rvalue is based on whether or not the expression has an identity. Something has an identity when it has a name or a unique memory location. Another test would be, can you take the address of the expression.

  • lvalue
    • has an identity
    • expression which can not be moved
     
  • rvalue
    • may or may not have an identity
    • expression can be moved

Typical examples of rvalue expressions are: a literal such as 42, true, or a nullptr.

Given the following code, the expressions button and *button both have an value category of lvalue.

QWidget *button = new QWidget;

Give the following code, someVar is an lvalue and 35 is an rvalue.

int someVar = 35;

The following code is not legal since 35 is an rvalue and located on the left side of the expression.

int 35 = someVar;

References

References were initially introduced in C++ to support operator overloading. To support pass by reference efficiently a new reference data type was added to C++11. The rvalue reference data type is what allows move semantics to work.

Using a reference data type to access an object is the same as using the original object. You should not think of a reference data type as a pointer data type. A pointer can point to nothing (nullptr) whereas a reference must always refer to an object. There is no "reference arithmetic".

There are three different reference data types in C++. When working with references you should be specific with your terminology and try not to just say "reference" since this is ambiguous.

  • lvalue reference
    QString & data;
    • Caller method or function will observe any changes made in the called method or function.
    • A method or function which takes an lvalue reference can be passed an lvalue but not an rvalue.
     
  • const reference
    const QString & data;
    • Called method or function can not modify the data or object it received.
    • A method or function which takes a const reference can be passed an lvalue or an rvalue.
     
  • rvalue reference
    QString && data;
    • Caller method or function gives away the data or object and is not allowed to access it anymore. The called method can change the data or discard it. Any changes made can not be observed by the caller.
    • A method or function which takes an rvalue reference can be passed an rvalue but not an lvalue.

If you think rvalue reference whenever you see && in a type declaration you will misread C++11. The syntax && might actually mean &. If a variable or parameter is declared to have type T && for some deduced type T, that variable or parameter is called a forwarding reference.

Move Semantics

The general idea of move semantics is for a method which no longer needs some data element to surrender the data and give it away to another method. Once the data is moved it can not be used, accessed, or read in the method which gave up the data.

The following is a simple example of move semantics using QVector. The expression myFruit has a value category of lvalue. The function std::move is part of the C++ standard library. It is used to relinquish ownership of an expression and the result has a value category of rvalue. The parameter to std::move can have any value category.

QVector<QString> data = { "grape", "pear", "orange" );
QString myFruit = "strawberry";
data.append( std::move(myFruit) );

The append method which is called in the code shown above, is the overloaded version which takes an rvalue reference as its parameter. Since the parameter to append is an rvalue reference, it can only receive an expression which has a value category of an rvalue.

  • you can not pass an rvalue expression to a method or function which takes an lvalue reference
  • you can not pass an lvalue expression to a method or function which takes an rvalue reference
void QVector::append ( T && value )

The && in the parameter list for append() is the notation which indicates this method takes its argument as an rvalue reference. You must therefore pass this overload of append an expression which can be bound to an rvalue reference.

The value of "strawberry" is now moved into the QVector and the variable myFruit is no longer valid to use. Please be aware that using the expression myFruit after the move will not generate a compiler error but it will have unpredictable results.