JoelFernandes.org

Age is of no importance unless you're a cheese -- Billie Burke

Hello! I’m Joel and this my personal website built with Jekyll! I currently work at Google. My interests are scheduler, RCU, tracing, synchronization, memory models and other kernel internals. I also love contributing to the upstream Linux kernel and other open source projects.

Connect with me on Twitter, and LinkedIn. Or, drop me an email at: joel at joelfernandes dot org

Look for my name in the kernel git log to find my upstream kernel patches. Check out my resume for full details of my work experience. I also actively present at conferences, see a list of my past talks, presentations and publications.

Full list of all blog posts on this site:
  • 25 Jun 2023   SVM and vectors for the curious
  • 10 Jun 2023   SELinux Debugging on ChromeOS
  • 28 Apr 2023   Understanding Hazard Pointers
  • 25 Apr 2023   PowerPC stack guard false positives in Linux kernel
  • 24 Feb 2023   Getting YouCompleteMe working for kernel development
  • 29 Jan 2023   Figuring out herd7 memory models
  • 13 Nov 2022   On workings of hrtimer's slack time functionality
  • 25 Oct 2020   C++ rvalue references
  • 06 Mar 2020   SRCU state double scan
  • 18 Oct 2019   Modeling (lack of) store ordering using PlusCal - and a wishlist
  • 02 Sep 2019   Making sense of scheduler deadlocks in RCU
  • 23 Dec 2018   Dumping User and Kernel stacks on Kernel events
  • 15 Jun 2018   RCU and dynticks-idle mode
  • 10 Jun 2018   Single-stepping the kernel's C code
  • 10 May 2018   RCU-preempt: What happens on a context switch
  • 11 Feb 2018   USDT for reliable Userspace event tracing
  • 08 Jan 2018   BPFd- Running BCC tools remotely across systems
  • 01 Jan 2017   ARMv8: flamegraph and NMI support
  • 19 Jun 2016   Ftrace events mechanism
  • 20 Mar 2016   TIF_NEED_RESCHED: why is it needed
  • 25 Dec 2015   Tying 2 voltage sources/signals together
  • 04 Jun 2014   MicroSD card remote switch
  • 08 May 2014   Linux Spinlock Internals
  • 25 Apr 2014   Studying cache-line sharing effects on SMP systems
  • 23 Apr 2014   Design of fork followed by exec in Linux
  • Most Recept Post:

    C++ rvalue references

    | Comments

    The author works in the ChromeOS kernel team, where most of the system libraries, low-level components and user space is written in C++. Thus the writer has no choice but to be familiar with C++. It is not that hard, but some things are confusing. rvalue references are definitely confusing.

    In this post, I wish to document rvalue references by simple examples, before I forget it.

    Refer to this article for in-depth coverage on rvalue references.

    In a nutshell: An rvalue reference can be used to construct a C++ object efficiently using a “move constructor”. This efficiency is achieved by the object’s move constructor by moving the underlying memory of the object efficiently to the destination instead of a full copy. Typically the move constructor of the object will copy pointers within the source object into the destination object, and null the pointer within the source object.

    An rvalue reference is denoted by a double ampersand (&&) when you want to create an rvalue reference as a variable.

    For example T &&y; defines a variable y which holds an rvalue reference of type T. I have almost never seen an rvalue reference variable created this way in real code. I also have no idea when it can be useful. Almost always they are created by either of the 2 methods in the next section. These methods create an “unnamed” rvalue reference which can be passed to a class’s move constructor.

    When is an rvalue reference created?

    In the below example, we create an rvalue reference to a vector, and create another vector object from this.

    This can happen in 2 ways (that I know off):

    1. Using std::move

    This converts an lvalue reference to an rvalue reference.

    Example:

    #include <iostream>
    #include <vector>
    
    int main()
    {
        int *px, *py;
        std::vector<int> x = {4,3};
        px = &(x[0]);
     
        // Convert lvalue 'x' to rvalue reference and pass
        // it to vector's overloaded move constructor.
        std::vector<int> y(std::move(x)); 
        py = &(y[0]);
    
        // Confirm the new vector uses same storage
        printf("same vector? : %d\n", px == py); // prints 1
    }
    

    2. When returning something from a function

    The returned object from the function can be caught as an rvalue reference to that object.

    #include <iostream>
    #include <vector>
    
    int *pret;
    int *py;
    
    std::vector<int> myf(int a)
    {
        vector<int> ret;
    
        ret.push_back(a * a);
    
        pret = &(ret[0]);
    
        // Return is caught as an rvalue ref: vector<int> &&
        return ret;
    }
    
    int main()
    {
        // Invoke vector's move constructor.
        std::vector<int> y(myf(4)); 
        py = &(y[0]);
    
        // Confirm the vectors share the same underlying storage
        printf("same vector? : %d\n", pret == py); // prints 1
    }
    

    Note on move asssignment

    Interestingly, if you construct vector ‘y’ using the assignment operator: std::vector<int> y = myf(4);, the compiler may decide to use the move constructor automatically even though assignment is chosen. I believe this is because of vector’s move assignment operator overload.

    Further, the compiler may even not invoke a constructor at all and just perform RVO (Return Value Optimization).

    Quiz

    Question:

    If I create a named rvalue reference using std::move and then use this to create a vector, the underlying storage of the new vector is different. Why?

    #include <iostream>
    #include <vector>
    
    int *pret;
    int *py;
    
    std::vector<int> myf(int a)
    {
        vector<int> ret;
    
        ret.push_back(a * a);
    
        pret = &(ret[0]);
    
        // Return is caught as an rvalue ref: vector<int> &&
        return ret;
    }
    
    int main()
    {
        // Invoke vector's move constructor.
        std::vector<int>&& ref = myf(4);
        std::vector<int> y(ref); 
        py = &(y[0]);
    
        // Confirm the vectors share the same underlying storage
        printf("same vector? : %d\n", pret == py); // prints 0
    }
    

    Answer

    The answer is: because the value category of the id-expression ‘ref’ is lvalue, the copy constructor will be chosen. To use the move constructor, it has to be std::vector<int> y(std::move(ref));.

    Conclusion

    rvalue references are confusing and sometimes the compiler can do different optimizations to cause further confusion. It is best to follow well known design patterns when designing your code. It may be best to also try to avoid rvalue references altogether but hopefully this article helps you understand it a bit more when you come across large C++ code bases.