I was doing some experiments to see when copy is performed apart from copy elision, RVO, NRVO cases.
So I've written some code like this:
class X {
public:
X() { std::cout << "Default constructor" << std::endl; }
X(const X&) { std::cout << "Copy constructor" << std::endl; }
X(X&&) { std::cout << "Move constructor" << std::endl; }
X& operator=(const X) {
std::cout << "Assignment operator" << std::endl;
return *this;
}
X& operator=(X&&) {
std::cout << "Move assignment operator" << std::endl;
return *this;
}
~X() { std::cout << "Destructor" << std::endl; }
};
class Y {
private:
X x;
public:
const X& getX() const {
std::cout << "getX" << std::endl;
return x;
}
};
int main() {
Y y;
std::cout << "assign to ref" << std::endl;
const X& x1 = y.getX();
(void)x1;
std::cout << "assign to const" << std::endl;
const X x2 = y.getX();
return 0;
}
and I receive the following as output:
Default constructor
assign to ref
getX
assign to const
getX
Copy constructor
Destructor
Destructor
Both when compiled with gcc or clang with -O3 and tried -std=c++{11,14,17} all produced the same output.
Which surprised me was, I wasn't expecting any copy to be performed when using y.getX(); to a const variable. It is something I used frequently just to ease my access to that variable and its members in the following code, but I wasn't doing it over a const reference instead I was just using const hoping the compiler would regard it just as a renaming.
Does anyone knows why exactly is that copy performed? Only reason that comes to my mind is that it is to make code thread-safe. If there are multiple threads working with object y, then my assignment to const would not be that const after all. Since it would just reference the member x in object y. Which might be changed by other threads. But I am not sure whether that's the real intention or not.