I've written a class similar to the following:
class ScriptThread {
public:
ScriptThread(): mParent() {}
private:
ScriptThread(ScriptThread *parent): mParent(parent) {}
public:
ScriptThread(ScriptThread &&rhs);
ScriptThread &operator = (ScriptThread &&rhs);
// copy constructor/assignment deleted implicitly
ScriptThread &execute(const Script &script);
ScriptThread spawn();
ScriptThread spawn(const Script &script);
private:
ScriptThread *mParent;
};
ScriptThread &ScriptThread::execute(const Script &script) {
// start executing the given script
return *this;
}
ScriptThread ScriptThread::spawn() {
// create a ScriptThread with "this" as its parent
return ScriptThread(this);
}
ScriptThread ScriptThread::spawn(const Script &script) {
// convenience method to spawn and execute at the same time
return spawn().execute(script); // ERROR: "use of deleted function"
}
As written, g++ fails to compile it at the line marked "ERROR", claiming that it's trying to use the (deleted) copy constructor. However, if I replace the last function with this:
ScriptThread ScriptThread::spawn(const Script &script) {
ScriptThread thread = spawn();
thread.execute(script);
return thread;
}
It compiles without an error. Even after referring to a number of articles, references, and other SO questions, I don't understand: why does the first invoke the copy constructor at all? Isn't the move constructor enough?
ScriptThread
is noncopyable (the implicit copy constructor/assignment operators are defined as deleted because you declared move constructor/assignment). In spawn()
, your original implementation:
ScriptThread ScriptThread::spawn(const Script &script) {
return spawn().execute(script);
}
is attempting to construct a ScriptThread
from an lvalue reference (execute
returns a ScriptThread&
). That will call the copy constructor, which is deleted, hence the error.
However, in your second attempt:
ScriptThread ScriptThread::spawn(const Script &script) {
ScriptThread thread = spawn();
thread.execute(script);
return thread;
}
we run into the rule, from [class.copy]:
When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
Even though thread
is an lvalue, we perform overload resolution on the constructor of ScriptThread
as if it were an rvalue. And we do have a valid constructor for this case: your move constructor/assignment.
That's why the replacement is valid (and uses move construction), but the original failed to compile (because it required copy construction).