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;
}
Related
The question holds for both stacks and queues, but I will just allude to a stack here for simplicity.
Assuming that we push non-const objects into std::stack, is it safe, when we're popping from the stack, to move the object at the top of the stack into a temporary variable before popping like in the following:
std::stack<std::string> st;
st.emplace("asdf");
auto popped = std::move(st.top());
st.pop();
Yes, it is safe, if you use the non-const version of the stack. (For a const version of the stack, you will most likely copy the object or get a compile error)
Since std::stack returns a non-const reference to an object, you are free to modify it. This includes moving out from the object.
Recall that moving from an object keeps it in a valid state (at least, if the class is implemented correctly). It does not destroy it. It is still there on the top of your stack. It just doesn't contain any definite value. The follow-up pop will call a proper destructor on it without a problem.
Note, that the moving-out is not allowed for std::priority_queue, because that kind of container actually cares about the contents. And - for that reason it just returns a const-reference, which cannot be used to move things out from it.
In response to Iamanon's observation that std::move can be perfomed on a const reference. In fact you can do that. Usually it is not useful though and it will usually reduce to a copy, instead of move. Consider the following example:
#include <iostream>
#include <string>
#include <stack>
class Foo {
public:
Foo() {
std::cout << "created\n";
}
~Foo() {
std::cout << "destroyed " << value << "\n";
}
Foo(const Foo& other) : value(other.value) {
std::cout << "copied\n";
}
Foo(Foo&& other) : value(other.value) {
other.value++;
std::cout << "moved\n";
}
Foo(const Foo&& other) : value(other.value) {
std::cout << "const-moved\n";
}
int value = 0;
};
int main()
{
std::stack<Foo> st;
st.emplace();
const std::stack<Foo>& cst = st;
auto popped = std::move(cst.top());
st.pop();
}
If you run the above, a const-moved version will be used. However, as you implement your const-move constructor you will realize that you cannot really do much better than your regular copy constructor. That is because other remain immutable. For example, you cannot take ownership of anything other holds, and reset it within other.
And if you remove the const-move constructor, the compiler will use the regular copy constructor.
If the copy-constructor is deleted, and only move-constructor is provided - then the compiler will throw an error at you.
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.
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
I would like to make sure destructor side effects are retained in a function which is a candidate for RVO. My goal is to snapshot the stack at entry and exit and have the expected stack variables present. This code seems to work for C++11 without using compiler specific options but I don't know a way to do this in earlier versions without adding spurious instances of Test to create multiple return paths. Is there some technique and does this always work for c++11?
class Test {
public:
int m_i;
Test() { m_i = 0; cout << "def_ctor" << endl; }
Test(const Test& arg) { this->m_i = arg.m_i; cout << "copy_ctor" << endl;}
~Test() { cout << "dtor needed for side effects" << endl; }
};
Test foo() {
Test ret;
return std::move(ret);
}
int main()
{
Test x=foo();
}
std::move isn't magic, it's just a function that returns a reference to its argument, so you should be able to do the same in any version of C++
template<typename T>
const T&
defeat_rvo(const T& t)
{ return t; }
Test foo() {
Test ret;
return defeat_rvo(ret);
}
I think you can also do it more directly, by just returning a reference:
Test foo() {
Test ret;
const Test& ref = ret;
return ref;
}
The copy elision rules say that the local object can be constructed directly in the return value when the expression in the return statement is "the name of a non-volatile automatic object", which is not the case here, as it's a reference to the object, not the name of the object itself. I'm less certain about that case, but a cast should work:
Test foo() {
Test ret;
return static_cast<const Test&>(ret);
}
This definitely isn't the name of the object, or the name of an alias to the object, it's a cast expression.
Don't return an object you do not want to be elided; any such change is going to be fragile under future maintenance or refactoring.
In general, an object should support elision in the cases where it can occur; this places semantic restrictions on what a move or copy construction can be, and what a destructor does.
Violating these semantic restrictions is easy (template<class T> T copy_of(T const& t){return t;}, then return copy_of(whatever);, or a static_cast, or whatever). It being easy does not mean it is safe, from a code maintainability perspective.
int value = 5; // this type of assignment is called an explicit assignment
int value(5); // this type of assignment is called an implicit assignment
What is the difference between those, if any, and in what cases do explicit and implicit assignment differ and how?
http://weblogs.asp.net/kennykerr/archive/2004/08/31/Explicit-Constructors.aspx
EDIT: I actually just found this article, which makes the whole thing a lot clearer... and it brings up another question, should you (in general) mark constructors taking a single parameter of a primitive type - numeric/bool/string - as explicit and leave the rest as they are (of course keeping watch for gotchas such as constructors like (int, SomeType = SomeType())?
Neither of these is an assignment of any kind -- they're both initialization. The first uses copy initialization, and the second direct initialization. (FWIW, I'm pretty sure I've never heard the terms "explicit assignment" or "implicit assignment" before).
Edit: (Mostly in response to Nathan's comment):
Here's a corrected version of the code from your comment:
#include <iostream>
struct Foo {
Foo() {
std::cout << "Foo::ctor()" << std::endl;
}
Foo(Foo const& copy) {
std::cout << "Foo::cctor()" << std::endl;
}
Foo& operator=(Foo const& copy) {
std::cout << "foo::assign()" << std::endl;
return *this;
}
};
int main(int, const char**) {
Foo f;
Foo b(f);
Foo x = b;
return 0;
}
The result from running this should be:
Foo::ctor()
Foo::cctor()
Foo::cctor()
If you run it and get an foo::assign(), throw your compiler away and get one that works (oh, and let us know what compiler it is that's so badly broken)!
They differ if a class has a constructor marked 'explicit'. Then, one of these does not work.
Otherwise, no difference.
Only the first one is an assignment. They are both initialization.
Edit: actually, I'm wrong. Neither are assignment.