verdude
verdude

Reputation: 57

Why is the copy constructor called when I am not passing an instance of the class?

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

Answers (4)

songyuanyao
songyuanyao

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.

LIVE DEMO from GCC

Upvotes: 4

AnT stands with Russia
AnT stands with Russia

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

Cheers and hth. - Alf
Cheers and hth. - Alf

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

Some programmer dude
Some programmer dude

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

Related Questions