Reputation: 57
I have a class Server which has a constructor:
Server::Server(int port) {
// initialize some class variables
port_ = port;
//...
}
I try to create an instance of the class like so:
int main(int argc, char** argv) {
int port = 3000;
Server server = Server(port);
}
And I get this compile error:
server_main.cpp:32:32: error: use of deleted function ‘Server::Server(const Server&)’
Server server = Server(port);
^
Now, I understand why the copy constructor was implicitly deleted, but why is it being called?
The error goes away if I add a copy constructor to the class. Is there any other way to avoid this?
Upvotes: 0
Views: 93
Reputation: 172884
Server server = Server(port);
is copy initialization; You're initializing server
from a temporary Server
.
copy elision might take place, but not guaranteed until C++17. Even copy-/move-constructor might not be called, but still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.
You could change it to direct initialization, which will invoke Server::Server(int)
directly:
Server server(port);
Or direct list initialization (Since C++11):
Server server{port};
EDIT
Since C++17, copy elision is guaranteed for this case.
Under the following circumstances, the compilers are required to omit the copy- and move- constructors of class objects even if copy/move constructor and the destructor have observable side-effects:
In initialization, if the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object:
T x = T(T(T())); // only one call to default constructor of T, to initialize x
So your code will work well with C++17; For copy elision being guaranteed, copy/move constructor is not required to be accessible.
Upvotes: 4
Reputation: 320381
From the very annoyingly pedantic point of view, many of the currently provided answers (if not all of them) are slightly misleading.
In C++ copy-initialization with the same type on the left-hand side and right-hand side is treated in a special way: it is immediately interpreted as an equivalent direct-initialization.
From [dcl.init]/16:
— If the destination type is a (possibly cv-qualified) class type:
— If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered...
This means that your copy-initialization
Server server = Server(port);
is actually handled as direct-initialization
Server server(Server(port));
and is further processed in accordance with the rules of direct-initialization.
Rules of direct-initialization say that overload resolution is used to choose a constructor and the constructor chosen in this case is the copy-constructor (which is deleted in your case, hence the error).
So, the end result is the same - the copy constructor is required. But "branches" of the standard logic that make it required are not the ones responsible for copy-initialization, but rather the ones responsible for direct-initialization.
In this case the difference is purely conceptual. But back in the days of C++98 this obscure distinction payed important role in the functionality of [now forgotten] std::auto_ptr
pointer (re: auto_ptr_ref
and how it worked). This is actually often seen as an early idiomatic implementation of Move Constructor pattern (https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Move_Constructor).
A simple example that illustrates that special handling might look as follows
struct A
{
A() {}
A(A&) {}
A(int) {}
operator int() const { return 42; }
};
struct B
{
B();
operator int() const { return 42; }
};
int main()
{
A a1 = A(); // OK
A a2 = B(); // Error
}
Note that even though both classes on the right-hand side provide a user-defined conversion to int
, only the first initialization compiles and uses A::A(int)
constructor. The second one fails.
The second initialization proceeds in accordance with usual copy-initialization rules. And in order to succeed it needs two user-defined conversions (B -> int
and int -> A
), which cannot be done implicitly.
The first initialization is treated in accordance with direct-initialization rules, thus effectively making the int -> A
conversion explicit. This initialization now needs only one implicit user-defined conversion (A -> int
), which is fine.
Upvotes: 3
Reputation: 145204
Copy initialization, the =
syntax as in Server server = Server{port};
, requires that a copy constructor or a move constructor exists and is accessible.
Since your copy constructor doesn't exist, try to provide a move constructor.
If you can't, then your only recourse is to use direct initialization syntax, e.g. Server server{port};
Upvotes: 2
Reputation: 409146
Because you copy-initialize the server
object.
The definition
Server server = Server(port);
is equivalent to
Server server(Server(port));
You might want to use the constructor explicitly by doing
Server server(port);
Upvotes: 2