复制构造函数初始化列表

21

我知道如果在无参数构造函数的初始化列表中省略了成员,那么该成员的默认构造函数将被调用。

那么复制构造函数是否也会调用成员的复制构造函数,还是也会调用默认构造函数?

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()?
}

2
请参见:https://dev59.com/oXRB5IYBdhLWcg3wpYmo#563320 - Martin York
8个回答

33

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

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

除非另有规定,否则成员按它们出现在类中的顺序进行默认初始化。 (如果不能这样做,则程序无效。)因此,如果您定义自己的复制构造函数,现在就需要您根据需要调用任何成员复制构造函数。

以下是一个小程序,您可以将其复制粘贴到某个地方并随意更改:

#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);
}

原样输出如下:

In Foo::Foo()
In Bar::Bar()
In Baz::Baz()
Copying...
In Foo::Foo()
In Bar::Bar()
In Baz::Baz(const Baz&)

请注意,这里是默认初始化了Baz的成员。

如果注释掉显式复制构造函数,如下所示:

/*
Baz(const Baz& rhs) {
    std::cout << "In Baz::Baz(const Baz&)" << std::endl;
}
*/
输出将是这样的:
在 Foo::Foo() 中
在 Bar::Bar() 中
在 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()
在 Bar::Bar()
在 Baz::Baz()
复制...
在 Foo::Foo(const Foo&)
在 Bar::Bar()
在 Baz::Baz(const Baz&)
如你所见,一旦你显式声明了一个复制构造函数,就要负责复制所有类成员;现在是你的构造函数。
这适用于所有构造函数,包括移动构造函数。

如果一个成员是原始指针(例如void *)或int、double等,用户定义的复制构造函数会在进入if之前将它们赋值为0吗?如果用户在复制构造函数的初始化列表中没有为这些成员分配任何内容,那么会怎样? - Serge Rogatch
如果您没有明确初始化它,那么该值就像普通未初始化的变量一样不确定,并且读取它是未定义的行为。您必须显式地将指针初始化为null,将int初始化为0等。 - GManNickG
除非另有规定,否则成员变量将按照它们在类中出现的顺序进行默认初始化。这意味着仅非 POD 成员将被默认初始化吗? - pellucidcoder
1
@pellucidcoder 所有内容都将被默认初始化。对于非类类型,这并没有什么作用。对于类类型(POD或非POD),将执行默认构造函数的重载解析。另请参见https://en.cppreference.com/w/cpp/language/default_initialization - GManNickG

2

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

该句话涉及到IT技术相关内容。

2
详情请参见:C++ 中是否存在隐式默认构造函数? 简述:
  • 编译器生成的“默认构造函数”:使用每个成员的默认构造函数。
  • 编译器生成的“拷贝构造函数”:使用每个成员的拷贝构造函数。
  • 编译器生成的“赋值运算符”:使用每个成员的赋值运算符。

并不是真正的答案,OP在问题中的复制构造函数并非由编译器生成。 - 463035818_is_not_a_number

1
根据基础调用构造函数的初始化方式,成员构造函数将以相同的方式调用。例如,让我们从以下代码开始:
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

享受。


1

不在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;
}

1

是的。构造函数就是构造函数。


1
如果一个问题中包含“或”,回答“Yes”是什么意思? - mmmmmmmm
3
rstevens,在Charlie回答问题后不久,问题被编辑了。Charlie完美地回答了原问题。尽管如此,我已经编辑了我的答案,并认为它足够好了 :) - GManNickG

1

当编译器提供默认的复制构造函数时,您认为编译器会为成员变量做什么?它会进行复制构造

同样地,如果复制构造函数是用户定义的,并且如果省略了某些成员,则这些成员不能保持未初始化状态。类不变式在构造期间被建立并且必须不断维护。所以,编译器会替您完成这些工作。


-1 抱歉。编译器提供的默认复制构造函数 使用该成员自己的复制构造函数复制每个成员(在原始类型的情况下是按位复制)。 - j_random_hacker
是的,但我说的是同样的事情!“默认初始化”意味着通过成员复制构造函数进行复制。 - Abhay
我明白你的意思,但实际上,“默认初始化”这个术语在C++标准中有一个具体而明确的含义,即使用类型的默认值来初始化对象(好吧,稍微有点复杂,但总之是这样的...)。因此,你的描述有些误导。 - j_random_hacker
@Abhay:如果你把“默认初始化”改成“复制构造”,我就取消我的-1。 - j_random_hacker

1

拷贝构造函数并没有什么神奇的地方,除了编译器在需要时会自动添加它。但实际运行时,没有什么特别之处 - 如果您没有明确指定“使用这样那样的构造函数”,它将使用默认值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接