C++ 为什么在我不传递类的实例时调用复制构造函数?

C++ 为什么在我不传递类的实例时调用复制构造函数?,c++,constructor,initialization,C++,Constructor,Initialization,我有一个类服务器,它有一个构造函数: Server::Server(int port) { // initialize some class variables port_ = port; //... } 我尝试创建类的实例,如下所示: int main(int argc, char** argv) { int port = 3000; Server server = Server(port); } 我得到了这个编译错误: server_main.cp

我有一个类服务器,它有一个构造函数:

Server::Server(int port) {
    // initialize some class variables
    port_ = port;
    //...
}
我尝试创建类的实例,如下所示:

int main(int argc, char** argv) {
    int port = 3000;
    Server server = Server(port);
}
我得到了这个编译错误:

server_main.cpp:32:32: error: use of deleted function ‘Server::Server(const Server&)’
     Server server = Server(port);
                                ^
现在,我明白了为什么复制构造函数被隐式删除,但为什么要调用它


如果我向类中添加一个复制构造函数,错误就会消失。有没有其他方法可以避免这种情况?

因为您复制并初始化了服务器对象

定义

Server server = Server(port);
相当于

Server server(Server(port));
您可能希望通过执行以下操作显式使用构造函数

Server server(port);
服务器=服务器端口;是您正在从临时服务器初始化服务器

可能发生,但在C++17之前不能保证。即使是copy-/move构造函数也可能不会被调用,但仍然必须存在并可以访问,就好像根本没有进行优化一样,否则程序的格式就不正确

您可以将其更改为,这将直接调用Server::Serverint:

Server server(port);
或者从C++11开始:

Server server{port};
编辑

因为C++17,所以在这种情况下是有保证的

在下列情况下,编译器必须省略 类对象的复制和移动构造函数,即使复制/移动 构造函数和析构函数有明显的副作用:

在初始化中,如果初始值设定项表达式是prvalue,则 源类型的cv非限定版本与 类,初始值设定项表达式用于 初始化目标对象:

T x=TTT;//只有一次调用T的默认构造函数来初始化x

因此,您的代码可以很好地与C++17配合使用;为了保证复制省略,复制/移动构造函数不需要是可访问的

复制初始化,语法为Server=Server{port};,要求复制构造函数或移动构造函数存在且可访问

由于复制构造函数不存在,请尝试提供移动构造函数


如果不能,那么您唯一的方法就是使用直接初始化语法,例如服务器{port}

从令人恼火的迂腐观点来看,目前提供的许多答案(如果不是全部的话)都有点误导

<>在左侧和右侧的相同类型的C++复制初始化以一种特殊的方式处理:它立即被解释为等效的直接初始化。 从[dcl.init]/16:

-如果目标类型可能是cv限定类类型:

-如果初始化是直接初始化,或者 复制源的cv不合格版本的初始化 类型与的类相同,或是的类的派生类 目的地,构造函数被认为是

这意味着您的副本初始化

Server server = Server(port);
Server server(Server(port));
实际上是作为直接初始化处理的

Server server = Server(port);
Server server(Server(port));
并根据直接初始化规则进行进一步处理

直接初始化的规则说,重载解析用于选择构造函数,在本例中选择的构造函数是复制构造函数,在本例中被删除,因此出现错误

因此,最终结果是相同的-需要复制构造函数。但是,标准逻辑的分支使其成为必需的不是负责拷贝初始化的分支,而是负责直接初始化的分支

在这种情况下,差异纯粹是概念上的。但在C++98时代,这种模糊的区别在[现已被遗忘的]std::auto_ptr指针re:auto_ptr_ref的功能及其工作方式中扮演了重要角色。这实际上通常被看作是Move构造函数模式的早期惯用实现

下面是一个说明特殊处理的简单示例

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
}
请注意,尽管右侧的两个类都提供了用户定义的int转换,但只有第一个初始化编译并使用::Aint构造函数。第二个失败了

第二次初始化按照通常的复制初始化规则进行。为了成功,它需要两个用户定义的转换B->int和int->A,这不能隐式完成


第一次初始化按照直接初始化规则处理,因此有效地使int->A转换显式化。这个初始化现在只需要一个隐式的用户定义转换A->int,这很好。

我很确定这是因为它在右侧初始化变量,然后复制到变量注意,它不太可能被调用。你可以通过编写一个复制构造函数来测试你的假设。这可能是一个问题的一部分。只是为了学究,一般来说,T a=x;相当于T aTx;。所以Server=Serverport;实际上相当于服务器端口:你可以明白为什么复制艾利森是一件事。@GManNickG:Unles
在最新版本的文档中对其进行了更改,LHS和RHS上相同类型的复制初始化始终以特殊方式处理:T a=x;直接等于T ax;,不需要涉及一般的复制省略。复制构造函数仍然需要存在。@AnT:啊,我不知道@GManNickG:事实上,当涉及用户定义的转换时,这种模糊且大多数时候无关紧要的区别在某些上下文中可能会起到重要作用。我刚刚写了一个答案,提到了这个小细节。@songyuanyao C++1z在某些情况下引入了保证复制省略,我认为这是一个,这使得使用这种不可复制/不可移动类型的初始化成为可能。