L/R values, reference, copy and move semantics
C++STD:
- https://en.cppreference.com/w/cpp/language/value_category
- https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2010/n3092.pdf
SF ANSWER
BLOGS TO READ
- https://blog.vero.site/post/rvalue-references
- https://www.internalpointers.com/post/c-rvalue-references-and-move-semantics-beginners
- https://www.codeproject.com/Articles/453022/The-new-Cplusplus-11-rvalue-reference-and-why-you
I find it extremely painful to understand all those l,r,gl,x,pr values before
going through the move
semantics.
CONSTRUCTORS
- C++ constructors DOES NOT RETURN ANYTHING (not even void)
- constructor is a special method that is automatically called when an object is created.
- it’s illegal to have “uninitialized object”.
class A {/**/}; // illegal: can't find (or derive) constructor that takes no param A arr[100];
THE MOVE AND COPY SEMANTICS
CODE EXAMPLE ALL IN ONE NOTE: DERIVED FROM THIS ARTICLE BY TRIANGLES
#include <algorithm>
#include <cstddef>
#include <iostream>
using namespace std;
class Holder {
private:
int *m_data;
size_t m_size;
public:
// constructor
Holder(int size) {
m_data = new int[size];
m_size = size;
}
// destructor
~Holder() {
delete[] m_data;
}
// copy constructor: initialize [this] from already-existing object
// (other is read-only reference)
Holder(const Holder &other) {
m_data = new int[other.m_size];
std::copy(other.m_data, other.m_data + other.m_size, m_data);
m_size = other.m_size;
}
// copy assignment constructor: nuke [this] and re-initizlize from
// already-existing object
Holder &operator=(const Holder &other) {
if (this == &other) {
return *this;
}
// first nuke existing data
delete[] m_data;
// same as copy constructor
m_data = new int[other.m_size];
std::copy(other.m_data, other.m_data + other.m_size, m_data);
m_size = other.m_size;
return *this;
}
// move constructor : reuse resource from other, no need to copy
Holder(Holder &&other) {
m_data = other.m_data;
m_size = other.m_size;
// prevent the data from being freed when other goes out of scope
other.m_data = nullptr;
other.m_size = 0;
}
// move assignment operator
Holder &operator=(Holder &&other) {
// same as first step of assignment oplerator
if (this == &other)
return *this;
delete[] m_data;
// same as copy constructor
m_data = other.m_data;
m_size = other.m_size;
other.m_data = nullptr;
other.m_size = 0;
return *this;
}
};
// if you simply return Holder h(size) the compiler may inline this function
// even with -fno-elide-constructors
Holder createHolder(size_t size) {
Holder h(size);
return h;
}
int main() {
// default constructor
Holder h0(0x1);
Holder g1(0x2);
Holder g2(0x2);
Holder g3(0x2);
// copy constructor:
// h0 is a lvalue, therefore it's not safe to move
Holder h1(h0);
// default constructor in createHolder() to create the temporary RVALUE
// then move constructor to create h2;
// when move-constructor is not defined, this falls back to copy;
// finally destructor on the temporary value (out-of-scope).
Holder h2(createHolder(0x2));
// copy assignment because h1 is LVALUE thus unsafe to move
g1 = h1;
// move assignment because functurn returns RVALUE (safe to move)
// however with -fno-elide-copy there is an extra move-constructor
// in-between. WHY?
g2 = createHolder(500);
// move assignment with lvalue because we explicitly do so.
// std::move() also falls back to copy if move methods are not present
g3 = std::move(h1); // move assignment with lvalue
Holder g4(std::move(g3)); // move constructor with lvalue
return 0;
}
C++ gvalue, prvalue, xvalue, lvalue, rvalue (fuck me, fuck you all)
GLVALUE "Classical" notations of LVALUE (IN C) ┌────────────┐ cannot be function calls. │ LVALUE │ C++ extends it to include function calls │ │ │ │ Historically: expressions legal on │ │ the LHS of "=" are LVALUE │ ┌────────┼───┐ │ │ XVALUE │ │ "Classical" notations of LVALUES └───┼────────┘ │ are automatically legal on the RHS │ │ │ │ │ │ │ PRVALUE │ Hence the "pure" RVALUE └────────────┘ RVALUE
N3092 Ch.3.10 : you should not dig into these definitions because they suck, this is nothing near formalization
- An LVALUE could appear on the LHS of an assignment expression. It desinates an object or a function call (the return type in should be a lvalue reference)
- An XVALUE (eXpiring value) also refers to an object, usually near the end of its lifetime (fuck this definition.)
- A GLVALUE is an lvalue or an xvalue (fuck this definition, too)
- An RVALUE (so called, historically, because rvalues could appear on the right-hand side of an assignment expressions) is an xvalue, a temporary object (12.2) or subobject thereof, or a value that is not associated with an object.
- A PRVALUE (“pure” rvalue) is an rvalue that is not an xvalue.
- Classical “L VALUE” (in C)
- a place in the memory that can hold a value. A variable, a pointer dereference
expression using
*
, structure field reference using.
or->
, array-element reference using[]
(if the array is an lvalue). etc. - ISO 2011 C STD
- An lvalue is an expression (with an object type other than void) that potentially designates an object; if an lvalue does not designate an object when it is evaluated, the behavior is undefined.
notes on L-VALUE (C)
- C STD doesn’t specify exactly the
R-VALUE
- an array (without
[]
) identifies memory location but is not L-VALUE - L VALUE doesn’t have to be mutable (e.g.
const
) - function pointer and function calls (regardless of return) are not L-VALUE
- all L-VALUE are valid as R-VALUE (in the sense of valid on the right of assignments) but not vice versa.