Reference binding and the copy constructor/move constructor - c++

If I have a function like so:
int foo(std::vector<int>* integer1ArrayIn, * integer2ArrayIn) {
std::vector<int>& integer1Array = *integer1ArrayIn;
std::vector<int>& integer2Array = *integer2ArrayIn;
}
Will the reference integer1Array be calling a copy constructor/move constructor to copy over the elements of the passed in parameter?
Does binding a reference to a dereferenced pointer call the copy constructor?
In what circumstances does reference binding invoke a copy constructor?
Can someone explain what happens as this code executes in memory?
Thank you

No.
No, but it would crash badly if it was nullptr. Consider passing by reference whenever a parameter HAS to be there, and pass by pointer when a parameter MIGHT be there (but always verify for nullptr too!).
Whenever the l-value (in this case integer1Array and integer2Array) are pointers or references, it will never call copy/move constructor.
if you had std::vector integer1Array = *integer1ArrayIn it would effectively make a copy.
You can use Jonas's answer to play around with and see for yourself :)

1) No, there are no copies made. You can test it with a small program, like this.
#include <iostream>
struct foo
{
foo() { std::cout << "Constructor" << std::endl; }
foo(const foo&) { std::cout << "Copy constructor" << std::endl; }
foo& operator=(const foo&) { std::cout << "Copy assignment operator" << std::endl; }
};
int main() {
foo* A = new foo;
foo& B = *A;
delete A;
}
2) Beware of nullptrs! Otherwise all is fine.
3) Never (see answer by AlexG)
4) Not sure what you mean by "code executes in memory", since code is not executed in memory. If you mean what happens to the memory when the program is executed, then that's another story

Related

Why std::move doesn't avoid a second destruction?

I know ordinary std::vector::push_back() will copy the object. I hope this code would only destruct a only once, using std::move() and A(A&&) noexcept to avoid copying. But it doesn't seem to work.
Is there any way that I can construct an object before push_back() and move it into a vector perfectly?
#include <bits/stdc++.h>
using namespace std;
class A {
public:
A() { std::cout << "construct" << this << '\n'; }
A(A&&) noexcept { std::cout << "move" << this << "\n"; }
A(const A&) = delete;
~A() { std::cout << "destruct" << this << '\n'; }
};
std::vector<A> as;
void add(A&& a) {
std::cout << "add 1\n";
as.push_back(std::move(a));
std::cout << "add 2\n";
}
int main() {
add(A());
std::cout << "main2\n";
return 0;
}
Output:
construct0x16d20b1fb
add 1
move0x151e068b0
add 2
destruct0x16d20b1fb
main2
destruct0x151e068b0
I hope this code would only destruct a only once, using std::move() and A(A&&) noexcept to avoid copying.
Using Move constructor perverts copying but doesn't prevent creating new objects, you are creating an inline object with default constructor "cout : construct0x16d20b1fb" and then from that object's data your going to create a 'NEW' object and calling move constructor will
transform the ownership of the object's data/resources to the new object that is being 'Constructed' so that explain the "cout : move0x151e068b0" then the line is finished so your inline object is destroyed "cout " destruct0x16d20b1fb" then your program finishes "cout : destruct0x151e068b0" your object that made via move constructor is destroyed.
you are expecting the behavior of pointers from your vector of "Objects" which is supposed to hold actual objects not pointer to other objects so it needs to containt objects and each of objects that are in that vector has a different address, unless you create your object and use a vector pointers to objects.
By the way std::move is but a cast, it casts to rvalue references so when you are capturing with rvalue reference there's no need to cast it again, you should be using it like this:
A a_object1;
vector.push_back(std::move(a_object1));
and it will use the move constructor. although in this case it will cast it implicitly because you are capturing only by rvalue reference in add function unless you add an overload to this function that also takes reference there's no need.

Strange side effect from a copy constructor

This simple code:
#include <iostream>
#include <vector>
struct my_struct
{
int m_a;
my_struct(int a) : m_a(a) { std::cout << "normal const " << m_a << std::endl; }
my_struct(const my_struct&& other) : m_a(other.m_a) { std::cout << "copy move " << other.m_a << std::endl; }
my_struct(const my_struct &other) : m_a(other.m_a) { std::cout << "copy const " << other.m_a << std::endl; }
};
class my_class
{
public:
my_class() {}
void append(my_struct &&m) { m_vec.push_back(m); }
private:
std::vector<my_struct> m_vec;
};
int main()
{
my_class m;
m.append(my_struct(5));
m.append(std::move(my_struct(6)));
}
produces this output:
normal const 5
copy const 5
normal const 6
copy const 6
copy const 5
The first call to append creates the object, and push_back creates a copy. Likewise, the second call to append creates the object, and push_back creates a copy. Now, a copy constructor of the first object is mysteriously called. Could someone explain me what happens? It looks like a strange side effect...
Now, a copy constructor of the first object is mysteriously called. Could someone explain me what happens? It looks like a strange side effect...
When you call push_back on std::vector, vector may need to grow it's size as stated in the cppreference:
If the new size() is greater than capacity() then all iterators and references (including the past-the-end iterator) are invalidated. Otherwise only the past-the-end iterator is invalidated.
You can use reserve before pushing anything to your vector. Try this:
class my_class
{
public:
my_class()
{
m_vec.reserve(10); // Use any number that you want.
}
void append(my_struct &&m) { m_vec.push_back(m); }
private:
std::vector<my_struct> m_vec;
};
Few other issues with your program:
You need to fix signature of your move constructor as move constructor requires rvalue reference (more specifically, xvalue or prvalue). It should like this:
my_struct(my_struct&& other) noexcept : m_a(other.m_a)
{
std::cout << "copy move " << other.m_a << std::endl;
}
noexcept is required as we need to inform C++ (specifically std::vector) that move constructor and destructor does not throw, using noexcept. Then the move constructor will be called when the vector grows. See this.
The method append should be:
void append(my_struct &&m)
{
m_vec.push_back(std::move(m));
}
To know why we need to use std::move on rvalue reference, see this Is an Rvalue Reference an Rvalue?. It says:
Things that are declared as rvalue reference can be lvalues or rvalues. The distinguishing criterion is: if it has a name, then it is an lvalue. Otherwise, it is an rvalue.
If you don't use std::move, then copy constructor would be called.
That's just how std::vector works!
When you call push_back(), the underlying array needs to grow to make room for the new element.
So internally, a new larger array is allocated and all the elements of the previous smaller array are copied into the freshly created array. This also comes with some overhead. Now, you can use some techniques to optimize away the copies.
If you have an idea of how large the array could grow, you can use the reserve() method to ensure that no resizing will occur upto that many locations.
vct.reserve(5)
This is will ensure that no resizing will occur until 5 elements.
Also, you can use the emplace_back() function to avoid an additional copy. It constructs the object in place. Simply pass the constructor parameters of the object to emplace_back()

return const value prevent move semantics

I am a beginner in cpp so excuse me for this question.
I was reading that returning const val prevents move semantics.
therefore I dont understand why the following code is compiled and works normally. is it because only temporary object is being created? in what cases the move semantics cannot being done? thank you in advance!
#include <iostream>
using namespace std;
const string foo()
{
return string("hello");
}
int main()
{
string other = std::move(foo());
}
std::move is just a unconditional cast to rvalue. In your case the return value of std::move(foo()) was const std::string&&. And because move constructor does not take const argument, copy constructor was called instead.
struct C {
C() { std::cout << "constructor" << std::endl; }
C(const C& other) { std::cout << "copy constructor" << std::endl; }
C(C&& other) { std::cout << "move constructor" << std::endl; }
};
const C get() {
return C();
}
int main() {
C c(std::move(get()));
return 0;
}
I was reading that returning const val prevents move semantics. therefore I dont understand why the following code is compiled and works normally.
When move semantics are prevented by some mechanism, this doesn't necessarily mean that the code doesn't compile. Often, it compiles happily, but an expected move construction turns out to be a copy instead.
Example: a type has a user provided copy ctor, which disables compiler-generated move ctors. When we think we move-construct, we don't.
struct Test {
Test() = default;
Test(const Test&) {}
};
Test t1;
Test t2{std::move(t1)}; // Copies!
in what cases the move semantics cannot being done?
Coming to your example, something that is const-qualified can't be used to move-construct another object in any meaningful way. Move construction makes sense when resources can be easily transferred, but const-ness prevents that. Example: a type has compiler-generate move and copy constructors, but we can't move-construct from a const instance.
struct Test {
Test() = default;
};
const Test t1;
Test t2{std::move(t1)}; // Copies!
Besides, it doesn't make sense to move something that is returned by a function by value:
string other = std::move(foo());
When foo() returns by value, you can move-construct from it, unless the return type is const. Hence, to enable move-construction of other:
std::string foo();
string other = foo();
std::move doesn't actually move anything. It is just an "rvalue cast". You cast something to rvalue, and the move constructor / move assignment operator does the actual moving if possible. "If possible" part is the key. In your example the return value is already an rvalue, so std::move literally does nothing. You may even get warnings like "nothing is moved". That is because the move constructor of std::string takes an argument of type std::string&& not const std::string&&. Because of that, the copy constructor is called.

Did not get a compile time error after deleting copy/move constructors and assignment operators during passing object to function

I the following code I have explictly forbidden copying and moving Dummy object using delete specifier for copy and move constructors and for copy and move assignment operators:
#include <string>
#include <iostream>
struct Dummy
{
explicit Dummy(const std::string& value) : value_(value) {}
Dummy(const Dummy&) = delete;
Dummy& operator=(const Dummy&) = delete;
Dummy(Dummy&&) = delete;
Dummy& operator=(Dummy&&) = delete;
void print_this_address() const
{
std::cout << "this address: " << this << "\n";
}
std::string value_;
};
void foo(Dummy&& obj)
{
std::cout << obj.value_ << "\n";
obj.print_this_address();
}
int main()
{
Dummy obj("42");
obj.print_this_address();
foo(std::move(obj));
std::cout << obj.value_ << "\n";
return 0;
}
This code compiles and runs well and I'm getting the following output in my terminal:
this address: 0x7ffeeb6d2a20
42
this address: 0x7ffeeb6d2a20
42
Can someone explains for me what's happening in this code and why I didn't get compile error and have the same object in foo function as in main. function?
You never actually try to copy or move the object, which is why you don't get an error.
The parameter Dummy &&obj is an rvalue reference parameter, meaning it can bind to rvalues but not to lvalues. It's still a reference, just like const Dummy &obj would be (which would also work in your case).
What std::move does is not to move anything, it casts its parameter to an rvalue reference. This makes it possible to pass it to functions expecting an rvalue (for example, a move constructor). For more details have a look at the answer linked by StoryTeller.
It's not because you called std::move that your object has moved. It was just "prepared" to be moved.
As such, you prepared obj but never tried to move it somewhere else, hence the no compiler error, and as you never actually moved it (you just passed an rvalue reference to your object into foo), you get the same result, because obj never changed.

Object passed to std::move but not moved from?

I am reviewing some code like this, where A is a moveable type:
// Returns true exactly when ownership of a is taken
bool MaybeConsume(A&& a) {
if (some condition) {
Consume(std::move(a)); // ???
return true;
}
return false;
}
// ... elsewhere ...
A a;
if (!MaybeConsume(std::move(a))) {
a.DoSomething(); // !!!
}
Our static analysis tool complains that a is used after being moved (at !!!). IIUC std::move is only a static_cast, and the object a won't actually get gutted until a move constructor or assignment operator is called (presumably in Consume). Assuming MaybeConsume satisfies the contract in the comment,
Does this work?
Is it UB?
Is std::move at ??? a no-op?
(Probably this particular instance can be refactored to avoid the subtlety, but I would still like to ask for my own understanding).
It's a spurious warning from your static analysis tool.
Does this work?
Yes, MaybeConsume is doing what the comment says. It's only taking ownership of its argument when some condition is true (assuming Consume actually does move construct/assign from its argument).
std::move is indeed just a fancy static_cast<T&&> so MaybeConsume(std::move(a)) is not transferring ownership, you're simply binding a reference to MaybeConsume's parameter.
Is it UB?
No, you're not making use of a if MaybeConsume indicates it has assumed ownership of its argument.
Is std::move at ??? a no-op?
Well, it's a no-op because it's just a static_cast, but if you meant to ask whether it's unnecessary, then, no, it isn't. Within the body of MaybeConsume, a is an lvalue because it has a name. If the signature of Consume is void Consume(A&&), then the code won't compile without that std::move.
From the example usage you've shown, it seems you're not supposed to call MaybeConsume with a prvalue argument, since the caller should presumably use the argument in some other manner if the function returns false. If that's true, then you should change its signature to bool MaybeConsume(A&). This will probably make your static analysis tool happy because that would allow you to write if (!MaybeConsume(a)).
To understand why the static analysis tool raises a warning, one needs to think in the way a static analyzer does. When it sees a piece of code like below:
A a;
fun(std::move(a);
a.method();
It is not clear what might happen inside fun() call. To successfully perform method() on a depends some prerequisites being satisfied, that might not( or no longer) holds after call of fun(). While the programmer might well know that it is safe to call method(), the analyzer does not, so it raises a warning.
The following is only my own opinion. It is safer to just assume the ownership of a is wholly taken by fun(). To prevent confusion, it is better to enforce a borrow-and-return style, Thinking of it as if a friend borrows a book from you, you don't (can not) use that book until it is returned. Thus never risk oneself accidentally invoke an object that should be "dead" by then.
See the below demonstration code:
#include <iostream>
#include <utility>
#include <tuple>
#include<cassert>
struct A {
public:
int *p;
public:
A() {
p = new int();
assert(p != nullptr);
std::cout << p << std::endl;
std::cout << "default constrctor is called" << std::endl;
}
A(const A&) = delete;
A& operator=(const A&) = delete;
A(A&& _a): p(_a.p) {
_a.p = nullptr;
std::cout << p << std::endl;
std::cout << "move constructor is called" << std::endl;;
}
A& operator=(A&& _a) {
std::cout << "move assignment is called"<<std::endl;;
p = std::move(_a.p);
return *this;
}
void DoSomthing(){
std::cout << "do somthing is called" << std::endl;
*p = 100;
std::cout << "value of p is changed"<<std::endl;
}
};
std::tuple<A&&, bool> MaybeConsume(A&& a) {
if (1==2) {//try 1==1 alternatively
delete a.p;
a.p = nullptr;//consume
std::cout << "content consumed" << std::endl;
return std::make_tuple(Consume(std::move(a)), true);
}
else {
return std::make_tuple(std::move(a), false);
}
}
int main()
{
A a;
std::tuple<A&&, bool> t = MaybeConsume(std::move(a));
if (!(std::get<bool> (t))) {
A a1 = std::move(std::get<A&&>(t));
a1.DoSomthing();
}
return 0;
}