Are you curious to know where the move feature is used in your C++ projects, on your behalf?

Move semantics is a feature introduced in C++11 that allows more efficient transfer of resources (such as dynamic memory) from one object to another. It addresses the inefficiencies associated with deep copying objects, especially large ones, by allowing objects to “steal” the resources of other objects when possible, rather than duplicating them.

Before C++11, when you assigned one object to another, a copy constructor or assignment operator would be invoked, resulting in a deep copy of the object’s data. This process could be expensive, particularly for large objects or those containing dynamic memory allocations. This feature is particularly useful in scenarios where performance optimization is critical, such as in high-performance computing, game development, and resource-constrained environments.

Move semantics introduces the notion of “rvalue references” and a new concept called “move constructors” and “move assignment operators”.

Here’s how it works:

  1. Rvalue References (&&): Rvalue references allow you to bind to temporary objects (rvalues), which are often objects that are going to be destroyed or not needed anymore. They are denoted by the double ampersand &&.
  2. Move Constructors: A move constructor is a special constructor that takes an rvalue reference to another object of the same type, and “moves” the resources from that object into the new object. It does this by “stealing” the internal pointers or handles from the source object and nullifying them, leaving the source object in a valid but unspecified state. This is typically done using the std::move() function, which converts an lvalue into an rvalue.
  3. Move Assignment Operators: Similar to move constructors, move assignment operators allow the efficient transfer of resources between objects. They take an rvalue reference to another object of the same type and perform a move assignment operation, leaving the source object in a valid state.

However, do I need to master this feature to benefit from its significant performance added value? Fortunately, in many cases, the compiler does an amazing job on your behalf and uses this feature when it can do so.

Let’s take this basic sample:

class test
{

};
test f()
{
    test t;
    return t;
}

In this sample, the move feature is not explicitly used by the developer, but it is used implicitly by the compiler. Indeed, when a function returns a value by value, traditionally, a copy of the object is made, which involves allocating memory for the copy and then copying the contents of the original object to the newly allocated memory. However, with move semantics, instead of making a copy, the resources owned by the local object within the function are transferred to the object being returned, if possible.

How to detect where the compiler use this feature on your behalf?

1- Using clang -cc1 -ast-dump command

The Clang Abstract Syntax Tree (AST) dump command is a powerful tool provided by the Clang compiler front-end for C, C++, and Objective-C. It allows developers to inspect the internal representation of their source code as an Abstract Syntax Tree, which is a hierarchical representation of the structure of the source code.

Let’s execute this command on our basic sample

|-CXXRecordDecl 0x1ccdf79b618 <test.cpp:1:1, line:4:1> line:1:7 referenced class test definition
| |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
| | |-DefaultConstructor exists trivial constexpr defaulted_is_constexpr
| | |-CopyConstructor simple trivial has_const_param implicit_has_const_param
| | |-MoveConstructor exists simple trivial
| | |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
| | |-MoveAssignment exists simple trivial needs_implicit
| | `-Destructor simple irrelevant trivial needs_implicit
| |-CXXRecordDecl 0x1ccdf79b738 <col:1, col:7> col:7 implicit class test
| |-CXXConstructorDecl 0x1ccdf888b58 <col:7> col:7 implicit used constexpr test 'void () noexcept' inline default trivial
| | `-CompoundStmt 0x1ccdf889098 <col:7>
| |-CXXConstructorDecl 0x1ccdf888cc8 <col:7> col:7 implicit constexpr test 'void (const test &)' inline default trivial noexcept-unevaluated 0x1ccdf888cc8
| | `-ParmVarDecl 0x1ccdf888de8 <col:7> col:7 'const test &'
| `-CXXConstructorDecl 0x1ccdf888ec8 <col:7> col:7 implicit used constexpr test 'void (test &&) noexcept' inline default trivial
|   |-ParmVarDecl 0x1ccdf888fe8 <col:7> col:7 'test &&'
|   `-CompoundStmt 0x1ccdf8892a0 <col:7>
`-FunctionDecl 0x1ccdf8889d8 <line:5:1, line:9:1> line:5:6 f 'test ()'
  `-CompoundStmt 0x1ccdf8892f8 <line:6:1, line:9:1>
    |-DeclStmt 0x1ccdf8891f0 <line:7:5, col:11>
    | `-VarDecl 0x1ccdf888ad8 <col:5, col:10> col:10 used t 'test':'test' nrvo callinit
    |   `-CXXConstructExpr 0x1ccdf8891c8 <col:10> 'test':'test' 'void () noexcept'
    `-ReturnStmt 0x1ccdf8892e0 <line:8:5, col:12>
      `-CXXConstructExpr 0x1ccdf8892b0 <col:12> 'test':'test' 'void (test &&) noexcept'
        `-ImplicitCastExpr 0x1ccdf889228 <col:12> 'test':'test' xvalue <NoOp>
          `-DeclRefExpr 0x1ccdf889208 <col:12> 'test':'test' lvalue Var 0x1ccdf888ad8 't' 'test':'test'

As we can see the compiler generate for the class test the Move constructor and the Move assignement

| | |-MoveConstructor exists simple trivial
| | |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
| | |-MoveAssignment exists simple trivial needs_implicit

And for the return statement of the function f() the move constructor is called.

 `-ReturnStmt 0x1ccdf8892e0 <line:8:5, col:12>
      `-CXXConstructExpr 0x1ccdf8892b0 <col:12> 'test':'test' 'void (test &&) noexcept'
        `-ImplicitCastExpr 0x1ccdf889228 <col:12> 'test':'test' xvalue <NoOp>
          `-DeclRefExpr 0x1ccdf889208 <col:12> 'test':'test' lvalue Var 0x1ccdf888ad8 't' 'test':'test'

Thanks to the C++11 standard and modern compilers, they utilize the move feature in each case where it’s trivial to use, bringing added value in performance compared to C++ compilers before the C++11 standard.

2-Using CppDepend

CppDepend internally uses Clang to parse the source code, traverse the AST to obtain the maximum of useful data needed to calculate metrics, and detect the structure and dependencies of your codebase.

For the class test we can see the generated move constructor in the CppDepend class browser:

And to know where it’s used, we can just right click on the move constructor and select the methods using it

And here’s the result:

As we can see the function f() is using implicitly the move contructor.

CppDepend get the AST from clang and convert it to a suitable model that we can query using our code query language:

If you are curious to know where the move feature is used in your C++ projects, you can use one of these two possibilities to know more your codebase 🙂