Pergunta

This question is because of this question and the comments.

This example :

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

when compiled using g++ 4.6.1 like this :

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

produces next output :

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]

and the output is A catch.

I understand that the 1st catch block triggered because of the slicing problem.

  1. Where does it say about hidden copy constructor in the base class?
  2. Where does it say about this behaviour?

PS1 Please provide answers with quote from the standard.
PS2 And I am aware that the exceptions should be handled by const reference.

Foi útil?

Solução

1 Where does it say about hidden copy constructor in the base class?

It's not hidden as much as it is implicit.

Using n3290:

§ 12 Special member functions

1/ The default constructor (12.1), copy constructor and copy assignment operator (12.8), move constructor and move assignment operator (12.8), and destructor (12.4) are special member functions. [ Note: The implementation will implicitly declare these member functions for some class types when the program does not explicitly declare them. The implementation will implicitly define them if they are odr-used (3.2). See 12.1, 12.4 and 12.8. —end note ]

So, let us follow the pointer:

§ 12.8 Copying and moving class objects

7/ If the class definition does not explicitly declare a copy constructor, one is declared implicitly. [...]

8/ The implicitly-declared copy constructor for a class X will have the form

X::X(const X&)

if
— each direct or virtual base class B of X has a copy constructor whose first parameter is of type const B& or const volatile B&, and
— for all the non-static data members of X that are of a class type M (or array thereof), each such class type has a copy constructor whose first parameter is of type const M& or const volatile M&.

Otherwise, the implicitly-declared copy constructor will have the form

X::X(X&)

And there you have it. In your case, there is a copy constructor A::A(A const&) implicitly defined for you.


2 Where does it say about this behaviour?

Unsuprisingly, in the part devoted to exception handling.

§ 15.3 Handling an exception

3/ A handler is a match for an exception object of type E if

[...]

— the handler is of type cv T or cv T& and T is an unambiguous public base class of E, or

[...]

It is very similar to parameter passing in functions. Because B publicly inherits from A, an instance of B can be passed as a A const&. Since a copy constructor is not explicit (hum...), B is convertible into a A, and therefore, as for functions, a B can be passed where a A (no reference) is expected.

The Standard goes on:

4/ The handlers for a try block are tried in order of appearance. That makes it possible to write handlers that can never be executed, for example by placing a handler for a derived class after a handler for a corresponding base class.

Which is really what this warning is all about.

Outras dicas

In your case, same warning pops up even if you catch by const reference, where no slicing occurs. Your problem is that since B is a public subclass of A ==> every B is-a A, so it can be caught by the first handler. You should probably order handlers from most specific to least specific.

Also, you're printing "A" in both catch blocks.

The example you give doesn't really demonstrate slicing, it's simply warning you that since B is-a A, the catch(A) effectively hides the catch(B).

To see the effects of slicing, you would have to do something with the A in the catch:

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

Where does it say about hidden copy constructor in the base class?

The standard says nothing about a "hidden copy constructor." It does say stuff about an implicitly-defined copy constructor.

If you must have a quote:

If the class definition does not explicitly declare a copy constructor, there is no user-declared move constructor, one is declared implicitly.

In C++11, this can be declared default or delete, depending on the contents of the class in question. I'm not going to copy and paste the full list of reasons why a class might not be copyable. But suffice it to say, your class will get a default copy constructor.

Where does it say about this behaviour?

About what behavior? The behavior you see is exactly what you would expect to see in the following case:

void foo(A) {}

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

You get slicing, because the parameter is taken by value. Which requires a copy.

Exception handlers are not like function calls; C++ will not choose the closest match. It will choose the first valid match. And since B is an A, it therefore matches catch(A).

Again, if you need some kind of quote (though I don't know why; any basic C++ book that describes exception handling will tell you this):

The handlers for a try block are tried in order of appearance. That makes it possible to write handlers that can never be executed, for example by placing a handler for a derived class after a handler for a corresponding base class.

Note that they even give an example that is exactly your example.

And I am aware that the exceptions should be handled by const reference.

And this is why you take exceptions by reference.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top