什么是隐藏切片复制构造函数

What is hidden slicing copy constructor?

本文关键字:复制 构造函数 切片 隐藏 什么      更新时间:2023-10-16

这个问题是因为这个问题和评论。

这个例子:

#include <iostream>
struct A {
    A(int value) : m_value(value) { }
    int m_value;
};
struct B : A {
    B(int value) : A (value) { }
};
int main()
{
    try {
        throw B(5);
    }
    catch(A) {
        std::cout << "A catch" << std::endl;
    }
    catch(B) {
        std::cout << "B catch" << std::endl;
    }
}

当使用g++ 4.6.1编译时,像这样:

g++ exception_slicing.cpp -ansi -pedantic -Wall -Wextra

产生下一个输出:

exception_slicing.cpp: In function 'int main()':
exception_slicing.cpp:20:5: warning: exception of type 'B' will be caught [enabled by default]
exception_slicing.cpp:17:5: warning:    by earlier handler for 'A' [enabled by default]

,输出为A catch

我理解第一个catch块是由于切片问题而触发的。

  1. 它在哪里说隐藏的复制构造函数在基类?
  2. 它在哪里说这种行为?

PS1请以标准报价提供答案。
我知道异常应该由const引用处理。

在您的例子中,即使通过const引用捕获,也会弹出相同的警告,而没有发生切片。您的问题是,由于BA ==>的公共子类,每个B都是一个A,因此它可以被第一个处理程序捕获。您可能应该按照最具体到最不具体的顺序排列处理程序。

另外,您在两个catch块中打印"A"

1基类中隐藏的复制构造函数在哪里?

与其说它是隐藏的,不如说是隐含的。

使用n3290:

§12特殊成员函数

1/默认构造函数(12.1)、复制构造函数和复制赋值操作符(12.8)、移动构造函数和移动赋值操作符(12.8)以及析构函数(12.4)都是特殊的成员函数。[注意:当程序没有显式声明这些成员函数时,实现将隐式声明某些类类型的成员函数。]如果它们被频繁使用,实现将隐式地定义它们(3.2)。参见12.1、12.4和12.8。-end note]

那么,让我们跟随指针:

§12.8复制和移动类对象

7/如果类定义没有显式声明复制构造函数,则隐式声明复制构造函数。[…]

8/类X隐式声明的复制构造函数的形式为

X::X(const X&)

如果
X的每个直接基类或虚基类B都有一个复制构造函数,其第一个形参类型为const B&const volatile B&,并且
对于类类型为M的X的所有非静态数据成员(或其数组),每个此类类类型都有一个复制构造函数,其第一个参数类型为const M&或const volatile M&

否则,隐式声明的复制构造函数的形式为

X::X(X&)

就是这样了。在您的例子中,有一个为您隐式定义的复制构造函数A::A(A const&)


2关于这种行为它说了什么?

不出所料,在专门讨论异常处理的部分。

§15.3异常处理

3/处理程序匹配E类型的异常对象,如果

[…]

-处理程序的类型是cv T或cv T&, TE的一个明确的公共基类,或

[…]

这与函数中的参数传递非常相似。因为B公开继承A,所以B的实例可以作为A const&传递。由于复制构造函数不是显式的(哼…),B可以转换为A,因此,对于函数来说,在需要A(不引用)的地方可以传递B

4/ try块的处理程序按照出现的顺序进行尝试。这使得编写永远无法执行的处理程序成为可能,例如,将派生类的处理程序放在相应基类的处理程序之后。

这就是这个警告的意义所在。

您给出的示例并没有真正演示切片,它只是警告您,由于B是- A, catch(A)有效地隐藏了catch(B)。

要查看切片的效果,必须对catch中的A做一些操作:

catch(A a) {
    // Will always print class A, even when B is thrown.
    std::cout << typeid(a).name() << std::endl;
}

基类中隐藏的复制构造函数在哪里?

标准没有提到"隐藏复制构造函数"。它确实说了一些关于隐式定义的复制构造函数的东西。

如果你必须有引号:

如果类定义没有显式声明复制构造函数,则没有用户声明的move构造函数,而是隐式声明的。

在c++ 11中,它可以声明为defaultdelete,这取决于所讨论的类的内容。我不打算复制粘贴一个类不可复制的完整原因列表。但只需说明,您的类将获得一个default复制构造函数。

关于这种行为它说了什么?

关于什么行为?您看到的行为正是您期望在以下情况下看到的:

void foo(A) {}
void main() {
  foo(B());
}

得到切片,因为参数是按值取的。需要一个副本。

异常处理程序不像函数调用;c++不会选择最接近的匹配。它将选择第一个有效匹配。由于B A,因此它匹配catch(A)

再次强调,如果你需要一些引用(虽然我不知道为什么;任何描述异常处理的c++基础书籍都会告诉你):

try块的处理程序按照出现的顺序进行尝试。这使得编写永远无法执行的处理程序成为可能,例如,将派生类的处理程序放在相应基类的处理程序之后。

请注意,他们甚至给出了一个与你的例子完全相同的例子。

并且我知道异常应该由const引用来处理。

这就是为什么通过引用接受异常