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

时间:2011-11-03 07:52:51

标签: c++ language-lawyer copy-constructor object-slicing

这个问题是因为this question and the comments

这个例子:

#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

我理解由于the slicing problem而触发了第一个catch块。

  1. 关于基类中隐藏的复制构造函数的说法在哪里?
  2. 这个行为在哪里说?
  3. PS1请提供标准引用的答案 PS2我知道异常应该由const引用处理。

4 个答案:

答案 0 :(得分:3)

在您的情况下,即使您通过const引用捕获,也会弹出相同的警告,其中不会发生切片。您的问题是BA ==&gt;的公共子类。每个B都是一个A,因此它可以被第一个处理程序捕获。您应该订购从最具体到最不具体的处理程序。

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

答案 1 :(得分:1)

你给出的例子并没有真正展示切片,它只是警告你,因为B是-A A,所以catch(A)有效地隐藏了捕获(B)。

要查看切片的效果,您必须对catch中的A执行某些操作:

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

答案 2 :(得分:1)

  

1关于基类中隐藏的拷贝构造函数的说法在哪里?

隐藏的内容并不隐藏。

使用n3290

  

§12特别会员职能

     

1 / 默认构造函数(12.1),复制构造函数和复制赋值运算符(12.8),移动构造函数和移动赋值运算符(12.8)以及析构函数(12.4)是特殊成员函数。 [注意:当程序没有显式声明它们时,实现将隐式声明某些类类型的这些成员函数。如果它们使用得很多,那么实现将隐式定义它们(3.2)。见12.1,12.4和12.8。 - 后注]

所以,让我们按照指针​​:

  

§12.8复制和移动类对象

     

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

     

8 / 类X的隐式声明的复制构造函数将具有

形式      

X::X(const X&)

     

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

     

否则,隐式声明的复制构造函数将具有

形式      

M&

你有它。在您的情况下,有一个隐式为您定义的复制构造函数X::X(X&)


  

2它对这种行为有什么看法?

毫无疑问,在致力于异常处理的部分。

  

§15.3处理例外

     

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

     

[...]

     

- 处理程序的类型为cv A::A(A const&)或cv T,而T&是明确的T公共基类,或

     

[...]

它与函数中的参数传递非常相似。由于E公开继承自B,因此A的实例可以作为B传递。由于复制构造函数不是显式的(hum ...),A const&可以转换为B,因此,对于函数,A可以在{{1}处传递(预期没有参考)。

标准继续:

  

4 / 尝试块的处理程序按外观顺序进行尝试。这使得编写永远不会执行的处理程序成为可能,例如通过在相应基类的处理程序之后放置派生类的处理程序。

这是警告的真正含义。

答案 3 :(得分:0)

  

它在基类中的隐藏拷贝构造函数中说了什么?

该标准没有提到“隐藏的拷贝构造函数”。它确实说明了一个隐式定义的复制构造函数。

如果你必须有一个引用:

  

如果类定义没有显式声明复制构造函数,则没有用户声明的移动构造函数,其中一个是隐式声明的。

在C ++ 11中,可以声明defaultdelete,具体取决于相关类的内容。我不打算复制并粘贴为什么类可能无法复制的完整列表。但足以说明,您的课程将获得default副本构造函数。

  

它对这种行为有什么看法?

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

void foo(A) {}

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

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

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

同样,如果你需要某种引用(虽然我不知道为什么;任何描述异常处理的基本C ++书都会告诉你这个):

  

尝试块的处理程序按外观顺序进行尝试。这使得编写永远不会执行的处理程序成为可能,例如通过在相应基类的处理程序之后放置派生类的处理程序。

请注意,他们甚至给出了一个完全符合您榜样的例子。

  

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

这是为什么你通过引用获取异常。