为什么隐式复制构造函数会调用基类的复制构造函数而定义的复制构造函数不会?

40
考虑一个类层次结构,其中 A 是基类,B 派生自 A
如果在 B 中未定义复制构造函数,则编译器将合成一个。当调用该复制构造函数时,它将调用基类的复制构造函数(即使用户未提供任何合成的复制构造函数)。
#include <iostream>

class A {
    int a;
public:
    A() {
        std::cout << "A::Default constructor" << std::endl;
    }

    A(const A& rhs) {
        std::cout << "A::Copy constructor" << std::endl;
    }
};

class B : public A {
    int b;
public:
    B() {
        std::cout << "B::Default constructor" << std::endl;
    }
};

int main(int argc, const char *argv[])
{
    std::cout << "Creating B" << std::endl;
    B b1;
    std::cout << "Creating B by copy" << std::endl;
    B b2(b1);
    return 0;
}

输出:

Creating B
A::Default constructor
B::Default constructor
Creating B by copy
A::Copy constructor
如果用户在类B中定义了自己的复制构造函数,则在调用该复制构造函数时,除非显式存在对基类复制构造函数的调用(例如在初始化列表中),否则将调用基类的默认构造函数。
#include <iostream>

class A {
    int a;
public:
    A() {
        std::cout << "A::Default constructor" << std::endl;
    }

    A(const A& rhs) {
        std::cout << "A::Copy constructor" << std::endl;
    }
};

class B : public A {
    int b;
public:
    B() {
        std::cout << "B::Default constructor" << std::endl;
    }
    B(const B& rhs) {
        std::cout << "B::Copy constructor" << std::endl;
    }
};

int main(int argc, const char *argv[])
{
    std::cout << "Creating B" << std::endl;
    B b1;
    std::cout << "Creating B by copy" << std::endl;
    B b2(b1);
    return 0;
}

输出:

Creating B
A::Default constructor
B::Default constructor
Creating B by copy
A::Default constructor
B::Copy constructor
我的问题是,为什么用户定义的复制构造函数不会默认调用基类的复制构造函数?

2
如果默认情况下是这样的,那么你如何指定你不想发生这种情况的情况呢? - PlasmaHH
@PlasmaHH 恰好也是我的第一个问题。 - Matt Phillips
2
因为标准委员会有选择,并且在我看来做出了最合乎逻辑的选择(如果您没有指定如何调用基类构造函数,则必须指的是默认构造函数(即没有参数的构造函数),因为您没有指定任何参数)。 - Martin York
2
同样的逻辑也适用于所有成员以及基类。 - Mooing Duck
你的问题就像“危险边缘”电视游戏节目一样,是我所找到的最清晰的答案。 - davidhigh
显示剩余3条评论
3个回答

9

所有基础子类构造函数都调用父类默认构造函数。这是标准的定义。正如你指出的,如果你想让派生类B调用A的复制构造函数,你必须明确地要求它。

#include <iostream>

class A {
int a;
public:
A() {
    std::cout << "A::Default constructor" << std::endl;
}

A(const A& rhs) {
    std::cout << "A::Copy constructor" << std::endl;
}
};

class B : public A {
int b;
public:
B() {
    std::cout << "B::Default constructor" << std::endl;
}
B(const B& rhs):A(rhs) {
    std::cout << "B::Copy constructor" << std::endl;
}
};

int main(int argc, const char *argv[])
{
std::cout << "Creating B" << std::endl;
B b1;
std::cout << "Creating B by copy" << std::endl;
B b2(b1);
return 0;
}

这是因为编译器无法知道每个不同构造函数应该调用哪个父类的构造函数,因此我们有了默认构造函数。对于其他所有构造函数,您必须明确声明它们。

输出:

Creating B
A::Default constructor
B::Default constructor
Creating B by copy
A::Copy constructor
B::Copy constructor

10
除了隐式复制构造函数之外,所有基础子类构造函数都调用父类默认构造函数。 :)! - Vincenzo Pii
隐式复制构造函数是编译器提供的最原始操作的显式名称,即将源到目标的位拷贝。在我看来,这只是一个花哨的名字。 - shakthi

9

这就是隐式复制构造函数的定义方式(调用默认构造函数没有意义)。一旦您定义了任何构造函数(包括复制构造函数或其他构造函数),它的正常自动行为就是调用默认的父类构造函数,因此更改特定用户定义构造函数的行为将会不一致。


3
简单(可能陈腐)的答案是因为你没有告诉它该怎么做。由于你正在编写派生复制构造函数,你完全控制它的行为。如果没有指定调用基类的构造函数,编译器会生成代码通过调用基类的默认构造函数来初始化基类。

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