【问题标题】:Copy constructor initialization lists复制构造函数初始化列表
【发布时间】:2009-04-16 04:06:49
【问题描述】:

我知道,如果您在无参数构造函数中将一个成员排除在初始化列表之外,则会调用该成员的默认构造函数。

复制构造函数是否同样调用成员的复制构造函数,还是它们也调用默认构造函数?

class myClass {
  private:
    someClass a;
    someOtherClass b;
  public:
    myClass() : a(DEFAULT_A) {} //implied is b()
    myClass(const myClass& mc) : a(mc.a) {} //implied is b(mc.b)??? or is it b()?
}

【问题讨论】:

标签: c++ copy-constructor


【解决方案1】:

显式定义的复制构造函数不会为成员调用复制构造函数。

当您进入构造函数的主体时,该类的每个成员都将被初始化。也就是说,一旦您到达{,就可以保证您的所有成员都已初始化。

除非指定,否则成员会按照它们在类中出现的顺序进行默认初始化。(如果不能,则程序格式错误。)因此,如果您定义自己的复制构造函数,现在由您根据需要调用任何成员复制构造函数。

这是一个小程序,您可以在某处复制粘贴并随意使用:

#include <iostream>

class Foo {
public:
    Foo() {
        std::cout << "In Foo::Foo()" << std::endl;
    }

    Foo(const Foo& rhs) {
        std::cout << "In Foo::Foo(const Foo&)" << std::endl;
    }
};

class Bar {
public:
    Bar() {
        std::cout << "In Bar::Bar()" << std::endl;
    }

    Bar(const Bar& rhs) {
        std::cout << "In Bar::Bar(const Bar&)" << std::endl;
    }
};

class Baz {
public:
    Foo foo;
    Bar bar;

    Baz() {
        std::cout << "In Baz::Baz()" << std::endl;
    }

    Baz(const Baz& rhs) {
        std::cout << "In Baz::Baz(const Baz&)" << std::endl;
    }
};

int main() {
    Baz baz1;
    std::cout << "Copying..." << std::endl;
    Baz baz2(baz1);
}

按原样打印:

在 Foo::Foo() 在酒吧::酒吧() 在 Baz::Baz() 复制... 在 Foo::Foo() 在酒吧::酒吧() 在 Baz::Baz(const Baz&)

请注意,它默认初始化Baz 的成员。

通过注释掉显式复制构造函数,例如:

/*
Baz(const Baz& rhs) {
    std::cout << "In Baz::Baz(const Baz&)" << std::endl;
}
*/

输出会变成这样:

在 Foo::Foo() 在酒吧::酒吧() 在 Baz::Baz() 复制... 在 Foo::Foo(const Foo&) 在 Bar::Bar(const Bar&)

它同时调用复制构造函数。

如果我们重新引入Baz 的复制构造函数并显式复制单个成员:

Baz(const Baz& rhs) :
    foo(rhs.foo)
{
    std::cout << "In Baz::Baz(const Baz&)" << std::endl;
}

我们得到:

在 Foo::Foo() 在酒吧::酒吧() 在 Baz::Baz() 复制... 在 Foo::Foo(const Foo&) 在酒吧::酒吧() 在 Baz::Baz(const Baz&)

如您所见,一旦您明确声明了复制构造函数,就负责复制所有类成员;它现在是你的构造函数。

这适用于所有构造函数,包括移动构造函数。

【讨论】:

  • 如果成员是原始指针(例如 void*)或 int、double 等,用户定义的复制构造函数会在输入 { 如果用户没有分配任何东西之前将 0 分配给它们吗?这些成员在复制构造函数的初始化列表中?
  • @SergeRogatch:如果你没有显式地初始化它,那么这个值就像一个普通的未初始化变量一样是未指定的,读取它是未定义的行为。您必须显式初始化指向 null 的指针、将 int 初始化为 0 等。
  • "除非指定,否则成员默认按照它们在类中出现的顺序进行初始化。" 只有非 POD 成员 不是默认初始化的吗?
  • @pellucidcoder 一切都将被默认初始化。对于非类类型,这没有任何作用。对于类类型(POD 或非 POD),执行默认构造函数的重载决议。另见en.cppreference.com/w/cpp/language/default_initialization
【解决方案2】:

对于任何具有默认构造函数的成员变量,如果您没有将对该成员变量的任何其他构造函数调用显式添加到初始化列表中,则会调用该默认构造函数。

【讨论】:

    【解决方案3】:

    详情见:Is there an implicit default constructor in C++?

    短:

    • 编译器生成的“默认构造函数”:使用每个成员的默认构造函数。
    • 编译器生成的“复制构造函数”:使用每个成员的复制构造函数。
    • 编译器生成的“赋值运算符”:使用每个成员的赋值运算符。

    【讨论】:

    • 确实是答案,有问题的 OP 复制构造函数不是编译器生成的。
    【解决方案4】:

    是的。演员就是演员。

    【讨论】:

    • -1:回答包含“或”的问题时,“是”是什么意思?
    • rstevens,查理回答后不久就编辑了问题。查理完美地回答了最初的问题。也就是说,我在下面编辑了我的答案,我认为这已经足够好了:)
    【解决方案5】:

    复制构造函数没有什么神奇之处,除了编译器会在需要时添加它。但在它的实际运行方式上,并没有什么特别之处——如果你没有明确说“使用这样那样的构造函数”,它将使用默认值。

    【讨论】:

      【解决方案6】:

      VC9 中没有。其他的不清楚。

      // compiled as: cl /EHsc contest.cpp
      //
      //    Output was:
      //    Child1()
      //    -----
      //    Child1()
      //    Child2()
      //    Parent()
      //    -----
      //    Child1(Child1&)
      //    Child2()
      //    Parent(Parent&)
      
      #include <cstdio>
      
      class Child1 {
          int x;
      public:
          static Child1 DEFAULT;
      
          Child1(){
              x = 0;
              printf("Child1()\n");
          }
      
          Child1(Child1 &other){
              x = other.x;
              printf("Child1(Child1&)\n");
          }
      };
      
      Child1 Child1::DEFAULT;
      
      class Child2 {
          int x;
      public:
          Child2(){
              x = 0;
              printf("Child2()\n");
          }
      
          Child2(Child2 &other){
              x = other.x;
              printf("Child2(Child2&)\n");
          }
      };
      
      class Parent {
          int x;
          Child1 c1;
          Child2 c2;
      
      public:
          Parent(){
              printf("Parent()\n");
          }
      
          Parent(Parent &other) : c1(Child1::DEFAULT) {
              printf("Parent(Parent&)\n");
          }
      };
      
      int main(){
          printf("-----\n");
          Parent p1;
          printf("-----\n");
          Parent p2(p1);
      
          return 0;
      }
      

      【讨论】:

        【解决方案7】:

        根据基调用构造函数的启动方式,成员的构造函数将以相同的方式调用。例如,让我们从:

        struct ABC{
            int a;
            ABC() : a(0)    {   printf("Default Constructor Called %d\n", a);   };
        
            ABC(ABC  & other )  
            {
                a=other.a;
                printf("Copy constructor Called %d \n" , a ) ;
            };
        };
        
        struct ABCDaddy{
            ABC abcchild;
        };
        

        你可以做这些测试:

        printf("\n\nTest two, where ABC is a member of another structure\n" );
        ABCDaddy aD;
        aD.abcchild.a=2;
        
        printf( "\n Test: ABCDaddy bD=aD;  \n" );
        ABCDaddy bD=aD; // Does call the copy constructor of the members of the structure ABCDaddy ( ie. the copy constructor of ABC is  called)
        
        printf( "\n Test: ABCDaddy cD(aD); \n" );
        ABCDaddy cD(aD);    // Does call the copy constructor of the members of the structure ABCDaddy ( ie. the copy constructor of ABC is  called)
        
        printf( "\n Test: ABCDaddy eD; eD=aD;  \n" );
        ABCDaddy eD;
        eD=aD;          // Does NOT call the copy constructor of the members of the structure ABCDaddy ( ie. the copy constructor of ABC is not called)
        

        输出:

        Default Constructor Called 0
        
        Test: ABCDaddy bD=aD;
        Copy constructor Called 2
        
        Test: ABCDaddy cD(aD);
        Copy constructor Called 2
        
        Test: ABCDaddy eD; eD=aD;
        Default Constructor Called 0
        

        享受吧。

        【讨论】:

          【解决方案8】:

          当编译器提供默认的cctor时,你认为编译器对成员变量做了什么?它复制构造它。

          同样,如果 cctor 是用户定义的,并且如果遗漏了一些成员,则这些成员不能未初始化。类不变量是在构造过程中建立的,并且必须不断维护。因此,编译器会为您执行此操作。

          【讨论】:

          • -1 抱歉。编译器提供的默认复制 ctor使用该成员自己的复制 ctor 复制每个成员(在原始类型的情况下是按位复制)。
          • 是的,但我说的是同样的事情! Default Initializes 表示通过成员 cctor 进行复制。
          • 我明白你在说什么,但实际上术语“默认初始化”在 C++ 标准中具有特定的、明确定义的含义,即使用类型的默认值初始化对象 (好吧,它稍微复杂一些,但无论如何......)所以你的描述有点误导。
          • @Abhay:如果您将短语“默认初始化”更改为“复制构造”,我将放弃我的 -1。
          猜你喜欢
          • 2012-03-19
          • 1970-01-01
          • 2012-04-11
          • 1970-01-01
          • 1970-01-01
          • 2017-05-02
          • 2018-06-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多