为什么在我没有传递类的实例时调用了复制构造函数?

时间:2016-10-11 06:38:25

标签: c++ constructor initialization

我有一个类Server,它有一个构造函数:

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);
                                ^

现在,我理解为什么隐式删除了复制构造函数,但为什么要调用它?

如果我在课程中添加了一个复制构造函数,那么错误就会消失。还有其他方法可以避免这种情况吗?

4 个答案:

答案 0 :(得分:4)

Server server = Server(port);copy initialization;您正在从临时server初始化Server

copy elision可能会发生,但在C ++ 17之前无法保证。甚至可能不会调用copy- / move-构造函数,但仍然必须存在且可访问(好像根本没有发生优化),否则程序就会格式不正确。

您可以将其更改为direct initialization,直接调用Server::Server(int)

Server server(port);

direct list initialization(自C ++ 11以来):

Server server{port};

修改

从C ++ 17开始,copy elision可以保证这种情况。

  

在下列情况下,编制者需要省略   类对象的复制和移动构造函数,即使复制/移动也是如此   构造函数和析构函数具有可观察到的副作用:

     
      
  • 在初始化中,如果初始化表达式是prvalue而且是   cv-源类型的非限定版本与该类相同   目的地的类,初始化表达式用于   初始化目标对象:

         

    T x = T(T(T())); // only one call to default constructor of T, to initialize x

  •   

所以你的代码适用于C ++ 17;对于保证复制省略,不需要复制/移动构造函数。

LIVE DEMO from GCC

答案 1 :(得分:3)

从非常讨厌的迂腐观点来看,目前提供的许多答案(如果不是全部的话)都有些误导。

在C ++中,左侧和右侧具有相同类型的复制初始化以特殊方式处理:它立即被解释为等效的直接初始化。

来自[dcl.init] / 16:

  

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

     

- 如果初始化是直接初始化,或如果是   copy-initialization所在的cv-nonqualified版本的源码   type与类的类相同,或者是类的派生类   目的地,构造函数被认为是......

这意味着您的复制初始化

Server server = Server(port);

实际上是作为直接初始化

处理的
Server server(Server(port));

并根据直接初始化规则进一步处理。

直接初始化规则说,重载决策用于选择构造函数,在这种情况下选择的构造函数是复制构造函数(在您的情况下删除,因此错误)。

所以,最终结果是一样的 - 复制构造函数是必需的。但是,需要它的标准逻辑的“分支”不是负责复制初始化的,而是负责直接初始化的那些。

在这种情况下,差异纯粹是概念性的。但是在C ++ 98时代,这种模糊的区别在[现在被遗忘] std::auto_ptr指针(re:auto_ptr_ref及其工作方式)的功能中发挥了重要作用。实际上,这通常被视为Move Constructor模式(https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Move_Constructor)的早期惯用实现。

一个简单的例子,说明特殊处理可能如下所示

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转换,但只有第一个初始化编译并使用A::A(int)构造函数。第二个失败了。

第二次初始化按照通常的复制初始化规则进行。并且为了成功,它需要两个用户定义的转换(B -> intint -> A),这是无法隐式完成的。

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

答案 2 :(得分:2)

因为您复制初始化server对象。

定义

Server server = Server(port);

相当于

Server server(Server(port));

您可能希望通过执行

显式使用构造函数
Server server(port);

答案 3 :(得分:2)

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

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

如果你不能,那么你唯一的办法是使用直接初始化语法,例如: Server server{port};