0

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.

Community
  • 1
  • 1
BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • 2
    -1: For needlessly asking for comments with quotes from the standard. This is not some obscure corner case that requires a deep understanding of subtle interactions of the C++ specification to get. It requires knowing two things: that unless your class is fundamentally uncopyable, it will get a copy constructor. And that exception handlers are tested in the order they are provided. Neither of these is particularly obscure. – Nicol Bolas Nov 03 '11 at 08:19
  • @NicolBolas Actually it is not a corner case, and your comment helped me figure it out. As you said there are no hidden copy constructors, however what is called here is `A(const A&)` copy constructor, which is implicitly declared by the compiler. I thought that somehow the compiler declares `A(const B&)`, and that confused me. – BЈовић Nov 03 '11 at 08:29

4 Answers4

3

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.

iammilind
  • 68,093
  • 33
  • 169
  • 336
Vlad
  • 18,195
  • 4
  • 41
  • 71
  • 1
    Here `public` inheritance is important; so edited your answer. For `private`/`protected` inheritance, this problem may not appear if it's in some 3rd function. – iammilind Nov 03 '11 at 08:05
  • Thanks! I actually wasn't aware of this behavior, but now that you've mentioned it, it makes perfect sense. – Vlad Nov 03 '11 at 08:09
1

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;
}
zennehoy
  • 6,405
  • 28
  • 55
  • The caught object is sliced, regardless whether it is used or not. – BЈовић Nov 03 '11 at 08:12
  • Yes, but slicing is not what is demonstrated by your example. If the object is unused (in the example it's not even named), it makes no difference whether it is sliced or not. – zennehoy Nov 03 '11 at 08:15
  • @zennehoy: It might, depending on what the slicing did and how the copy constructor works. Granted, in this simple case, it's meaningless. And one could (very easily) argue that if you've written a case where slicing a copy and immediately deleting it will somehow screw up your class, you've screwed up. But on a technical level, it does matter that slicing happened. – Nicol Bolas Nov 03 '11 at 08:22
  • @Nicol: True, the copy constructor could have side effects, in which case it would matter. The example as given clearly has a default copy constructor though, so the key issue demonstrated is catch-block ordering, not slicing. Reworded the answer accordingly. – zennehoy Nov 03 '11 at 08:31
1

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.

Community
  • 1
  • 1
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
0

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.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982