Can I throw a unique_ptr? - c++

I have started using C++ 11 and in particular using unique_ptr liberally to make code exception-safe and ownership more readable. This has generally worked well until I wanted to throw a unique_ptr. I have error code (thrown many places, caught in a single place) that creates complex state. Since the ownership of that dynamically allocated memory logically is being transferred from the thrower to the catcher, unique_ptr seemed like the appropriate type to indicate that and make it clear that the catcher had acquired a heap object. Didn't work, at least with the free Visual Studio 2013. Here's a boiled-down code example that no longer resembles anything useful but elicits the behavior:
// cl /nologo /EHsc /W4 test1.cpp
#include <memory>
using std::unique_ptr;
class IError
{
public:
virtual ~IError() {};
virtual void DoStuff();
};
unique_ptr<IError> Error();
int Foo() { throw Error(); }
int main(void)
{
try {
Foo();
}
catch(unique_ptr<IError> Report)
{
Report->DoStuff();
}
return 0;
}
And the compiler spews thusly:
test1.cpp
test1.cpp(13) : warning C4673: throwing 'std::unique_ptr<IError,std::default_delete<_Ty>>' the following types will n
ot be considered at the catch site
with
[
_Ty=IError
]
test1.cpp(13) : warning C4670: '_Unique_ptr_base<class IError,struct std::default_delete<class IError>,1>' : this bas
e class is inaccessible
test1.cpp(13) : error C2280: 'std::unique_ptr<IError,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,
std::default_delete<_Ty>> &)' : attempting to reference a deleted function
with
[
_Ty=IError
]
C:\bin\Visual Studio Express 2013\VC\INCLUDE\memory(1486) : see declaration of 'std::unique_ptr<IError,std::d
efault_delete<_Ty>>::unique_ptr'
with
[
_Ty=IError
]
Where did I go wrong?

As a surrogate I'll be using Rextester which has MSVC version 18.00.21005.1. For GCC 4.8.1 and Clang 3.5, I'll be using Coliru. Now, initially when giving a hasty answer, I said that unique_ptrs cannot be copied and so you should be catching them by reference. However it appears the error occurs when you throw the object in MSVC. So the above advice will only apply to GCC and Clang.
catch(unique_ptr<IError>& Report)
It appears that they differ in how MSVC handles copy/move elision and/or move semantics, I'm not good enough at C++ to be more specific than that, but let's show some compilable examples. First a basic struct with a deleted copy constructor:
#include <iostream>
struct D {
D() {};
D(const D& other) = delete;
D(D&& other) { std::cout << "call D move constructor... \n"; }
};
int main()
{
try {
throw D();
} catch(D const& d)
{
}
}
Regardless of optimization level, for both GCC and Clang, no output unless you also add -fno-elide-constructors to the invocation and we see that they both call the move constructor. For MSVC, we get this error:
source_file.cpp(22) : error C2280: 'D::D(const D &)' : attempting to reference a deleted function
source_file.cpp(7) : see declaration of 'D::D'
For a more complicated example, see Throwing movable objects. The question is two years old yet we observe the same behavior in that GCC and Clang both call the move constructor in certain situations but MSVC calls the copy constructor in all situations (GCC and Clang differ for the Throw with object not about to die anyhow (enter non-zero integer part.)
Throw directly:
C
caught: 007FFA7C
~
Throw with object about to die anyhow
C
c
~
caught: 007FFA74
~
Throw with object not about to die anyhow (enter non-zero integer)
C
c
caught: 007FFA70
~
1
~
TL;DR GCC and Clang will compile it but MSVC won't. A crappy workaround is to throw a pointer instead:
throw new unique_ptr<IError>;
catch(unique_ptr<IError>* Report);

the common rule with exception is "throw by value, catch by const reference". You can't copy a unique_ptr, I guess that's part of the problem.
Derive your class Error from std::exception, and throw that.

Your error is caused by the fact that std::unique_ptr (deliberately) does not have a copy constructor which the compiler attempts to call when catching the exception.
Note, in your catch clause any compiler must not attempt to move the std::unique object (there cannot be a move from the exception object).
On the one hand,
You can catch your exception by reference - the copying would be avoided in the catch clause. So specifically this error will go away.
Note,
If you catch by value, (since C++11) compilers may perform copy elision in a catch clauses if the copy elision would not change the observable behavior of the program for any reason other than skipping the copy constructor and the destructor of the catch clause argument (for example, if the catch clause argument is modified, and the exception object is rethrown with throw).
This is the case, but since it is a non-mandatory elision, the fact your compiler does not perform it does not violate the language standard.
On the other hand,
A type used for exceptions throwing must have a copy constructor.
Besides the fact that copy elision when both throwing and catching are only permitted but not obligated, there are some cases when copy elision is not sensible.
Even if you factually avoid copying in catch clauses in combination with copy elision or moving when throwing the exception, it is not correct for your type (class) used for exceptions to not have a copy constructor.

Related

Does noexcept apply to exceptions propagating from initializer lists

Lets say I have a constructor like so
Something(SomethingElse) noexcept : member{SomethingElse, something_that_might_throw()} {
...
}
Is a noexcept okay in this case if the construction of member can throw? In the above example the member variable member is of a type I do not know.
On a side note: Are there other edge cases one needs to worry about when using noexcept?
#UPDATE:(Based on your edit): The original answer applies to everything within the block-scope of the function (constructors inclusive, including constructor-initialization-list). Why don't you try the code and see. :-)
#Original Answer
Something(SomethingElse) : member{SomethingElse} noexcept {
...
}
Your code would not compile that way. noexcept comes before the colon :
Something(SomethingElse) noexcept : member{SomethingElse} {
...
}
To your question:
Is a noexcept okay in this case if the member class constructor can
throw?
No, it's not okay. The noexcept specifier is a promise that an exception will not leave that function or constructor. Should it leave, the runtime will terminate your program.
If a search for a matching exception handler leaves a function marked
noexcept or noexcept(true), std::terminate is called immediately.
This applies to the constructor.
Despite the try..catch block, the code below gets terminated:
struct Base {
Base(){}
Base(int){ throw int(5); }
};
struct Derived : public Base {
Derived(int a) noexcept : Base(a) {}
};
int main()
{
try{
Derived d(45);
} catch(...) {}
return 0;
}
Output:
terminate called after throwing an instance of 'int'
bash: line 7: 7454 Aborted (core dumped) ./a.out
See it Live on Coliru
But if you remove the noexcept specification, you wouldn't abruptly end your program, exception handling will continue normally. :-).
I'll leave you to think of the consequences if you do this kind of thing in production or even a large code base with many contributors. Do not use noexcept if you are unsure of the exception guarantees of all the statements in your function/constructor block
Yes, a noexcept on a constructor applies to base class/member construction.
If you need to deal with such a case, you probably want to use the little-known (and rarely used) function try block. The syntax looks something like this:
#include <iostream>
class bad_initializer {};
int do_throw() {
throw bad_initializer();
return 1;
}
class something {
int member;
public:
something() noexcept
try : member{do_throw()} // <-- initializer list
{
// ctor body goes here
}
catch(bad_initializer const &) {
std::cerr << "initialization threw\n";
}
};
int main() {
something s;
}
Now, the bad news: since you have a member that wasn't constructed, the catch block really has only a few options. Normal processing can't continue--an exception happened in constructing the object, which means the object can't ever finish construction. By the time you catch the exception, there's nothing you can do about that.
If it weren't noexcept, it could catch the exception, then do some processing (release any resources it did acquire successfully) then either rethrow the exception it caught, or throw a different exception (one that better reflected its inability to be constructed).
In this case, your meaningful choices are even more restricted: you can call terminate directly, or you can throw an exception, which will call terminate indirectly. About all the whole try/catch thing has done in this case is give you a chance to do a little processing before terminate gets called.
In general it's not okay, but in certain circumstances it may be
noexcept is a promise, similar to the const qualifier of member functions, but unlike const it's not enforced at compile time (the compiler will not issue an error and perhaps not even a warning, if a noexcept function may actually throw, but see below). Thus, you can declare a function noexcept even if a compiler-generated check would find that this function may throw and the code will compile (otherwise your question would be impossible).
However, that defeats the purpose of noexcept. The main purpose is to indicate (1) to the programmer that they can use a certain functionality without worrying about exception safety, and (2) to the compiler that it does not need to add code for stack unwinding. Instead, if an exception is thrown in a noexcept function, the run-time will invoke std::terminate(), as required by the standard.
So, in general your constructor should only be noexcept, if all its functionality, including the construction of bases and members is too.
However, there is an exception. If you know that some method will never throw given any input possible in the particular situation, even if it may throw under other circumstances and hence not be noexcept, you can call this method in a noexcept situation. For example,
// requires non-negative x, will throw std::runtime_error for negative x
int my_func(std::int64_t x);
struct my_struct {
std::int64_t q;
my_struct(int x) noexcept : q(std::int64_t(x)*std::int64_t(x)) {}
int member() const noexcept { return my_func(q); } // fine: q>=0
};
In the situation of your post, this means if member(somethingelse_type) is not noexcept, but you know (from conditions outside the code snipped in your post) that no exception will be thrown for the particular argument provided in this circumstance, you may declare Something(SomethingElse) noexcept.

Weird behavior with exceptions thrown from std::async, in MSVC 2015

I just upgrade from Visual Studio 2013 to 2015, and I'm running into a bunch of issues with things that used to work in 2013, but which do not in 2015.
For example, here's one that has me stumped. I created a test-case out of the original code.
Basically, the code runs some operations in a thread, via std::async(). Within the thread, exceptions might be thrown (A), which should be placed in the future object returned by std::async(). The weird thing is that in (B), the destructor of Ex is called, but the object is still thrown aftewards. In the try-block, when the ex (D) variable leaves the score, if 'mInts' vector (X) is a member, the program would crash. If I leave 'mInts' commented out, as below, I still get weird behavior. For example, this is what's printed with the code below: notice how the constructor is called one, but the destructor is called 4 times:
Output:
constructor
destructor
before exception
after exception
destructor
has exception
destructor
destructor
Code:
using FutureList = std::vector<std::future<void>>;
struct Ex {
Ex() {
std::cout << "constructor\n";
}
Ex(const Ex&) = delete;
Ex(Ex&&) {
std::cout << "move constructor";
}
~Ex() {
std::cout << "destructor\n";
}
void operator=(const Ex&) {
std::cout << "assign\n";
}
// std::vector<int> mInts; (X)
};
TEST(Explore, Test1) {
FutureList futures;
futures.push_back(
std::async(std::launch::async, []() {
throw Ex(); // (A)
}));
std::exception_ptr ex;
for (auto& i : futures) {
try {
i.get(); // (B)
std::cout << "Doesn't get here.\n";
}
catch (...) { // (C)
std::cout << "before exception\n";
ex = std::current_exception(); // (D)
std::cout << "after exception\n";
}
}
if (ex) {
std::cout << "has exception\n";
}
}
Actually, MSVC doesn't call the copy constructor in your example. There's no code to call; the function is deleted. It does something worse: it treats the class as being trivially copyable and does a memcpy on the object. That's the reason for the crash when you have a member of type std::vector<int>.
The problem is not directly related to std::async. It's caused by the implementation of std::exception_ptr. The shared state of a std::future uses an exception_ptr to store an exception, so using a future in the context of exceptions will trigger the problem. The issue can otherwise be reproduced when using only std::current_exception(), which involves a copy in VC 14's standard library implementation (the Standard allows that).
The problem is with the implementation of __ExceptionPtr::_CallCopyCtor in crt/src/stl/excptptr.cpp. It essentially does a test that goes like "Is there a copy constructor? No? Great, we can do a memcpy then!".
Another problem is that the test ignores access specifiers. If you provide a copy constructor but make it private, it will be called.
The test is done at runtime, so no chance for a compile-time error, and, unfortunately, that's the only way to do it: there is no general compile-time test that will tell what type of exception object an std::exception_ptr will point to in all cases.
The proposed resolution for Core issue 1863 aims to avoid this problem by requiring that
[...] the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible. [...]
The issue is in Ready status, which is one step away from adoption.
So, while this is definitely a problem with MSVC, it's also related to an open issue in the Standard. For now, it looks like a good idea to provide a copy constructor for exception objects, even if the Standard doesn't require it (yet).
Update: The resolution for issue 1863 has been adopted into the N4567 working draft, so compilers implementing the change are required to reject the code if a suitable constructor is not available.
It seems that MSVC 2015 still calls the copy constructor, even though it's marked deleted. To get around this issue, I defined the copy constructor.
The issue with the printout was because there was no printing in the copy constructor. I added some, and the constructor/destructor count matched.
Still, MSVC 2015 shouldn't be calling the copy-constructor if it's marked deleted. If it must be called, then it should issue an error.

Noexcept and copy, move constructors

Everywhere I look it seems to be the agreement that the standard library must call copy constructors instead of move constructors when the move constructor is noexcept(false).
Now I do not understand why this is the case. And futher more Visual Studio VC v140 and gcc v 4.9.2 seems to do this differently.
I do not understand why noexcept this is a concern of e.g. vector. I mean how should vector::resize() be able to give strong exception guarantee if T does not. As I see it the exception level of vector will be dependend on T. Regardless if copy or move is used.
I understand noexcept to solely be a wink to the compiler to do exception handling optimization.
This little program calls the copy constructor when compiled with gcc and move constructor when compiled with Visual Studio.
include <iostream>
#include <vector>
struct foo {
foo() {}
// foo( const foo & ) noexcept { std::cout << "copy\n"; }
// foo( foo && ) noexcept { std::cout << "move\n"; }
foo( const foo & ) { std::cout << "copy\n"; }
foo( foo && ) { std::cout << "move\n"; }
~foo() noexcept {}
};
int main() {
std::vector< foo > v;
for ( int i = 0; i < 3; ++i ) v.emplace_back();
}
This is a multi-faceted question, so bear with me while I go through the various aspects.
The standard library expects all user types to always give the basic exception guarantee. This guarantee says that when an exception is thrown, the involved objects are still in a valid, if unknown, state, that no resources are leaked, that no fundamental language invariants are violated, and that no spooky action at a distance happened (that last one isn't part of the formal definition, but it is an implicit assumption actually made).
Consider a copy constructor for a class Foo:
Foo(const Foo& o);
If this constructor throws, the basic exception guarantee gives you the following knowledge:
No new object was created. If a constructor throws, the object is not created.
o was not modified. Its only involvement here is via a const reference, so it must not be modified. Other cases fall under the "spooky action at a distance" heading, or possibly "fundamental language invariant".
No resources were leaked, the program as a whole is still coherent.
In a move constructor:
Foo(Foo&& o);
the basic guarantee gives less assurance. o can be modified, because it is involved via a non-const reference, so it may be in any state.
Next, look at vector::resize. Its implementation will generally follow the same scheme:
void vector<T, A>::resize(std::size_t newSize) {
if (newSize == size()) return;
if (newSize < size()) makeSmaller(newSize);
else if (newSize <= capacity()) makeBiggerSimple(newSize);
else makeBiggerComplicated(newSize);
}
void vector<T, A>::makeBiggerComplicated(std::size_t newSize) {
auto newMemory = allocateNewMemory(newSize);
constructAdditionalElements(newMemory, size(), newSize);
transferExistingElements(newMemory);
replaceInternalBuffer(newMemory, newSize);
}
The key function here is transferExistingElements. If we only use copying, it has a simple guarantee: it cannot modify the source buffer. So if at any point an operation throws, we can just destroy the newly created objects (keep in mind that the standard library absolutely cannot work with throwing destructors), throw away the new buffer, and rethrow. The vector will look as if it had never been modified. This means we have the strong guarantee, even though the element's copy constructor only offers the weak guarantee.
But if we use moving instead, this doesn't work. Once one object is moved from, any subsequent exception means that the source buffer has changed. And because we have no guarantee that moving objects back doesn't throw too, we cannot even recover. Thus, in order to keep the strong guarantee, we have to demand that the move operation doesn't throw any exceptions. If we have that, we're fine. And that's why we have move_if_noexcept.
As to the difference between MSVC and GCC: MSVC only supports noexcept since version 14, and since that is still in development, I suspect the standard library hasn't been updated to take advantage yet.
The core issue is that it's impossible to offer strong exception safety with a throwing move constructor. Imagine if, in vector resize, half way through moving the elements to the new buffer, a move constructor throws. How could you possibly restore the previous state? You can't use the move constructor again because, well, that could just keep throwing.
Copying works for strong exception safety guarantee regardless of it's throwing nature because the original state is not damaged, so if you can't construct the whole new state, you can just clean up the partially-built state and then you're done, because the old state is still here waiting for you. Move constructors don't offer this safety net.
It's fundamentally impossible to offer strongly exception safe resize() with a throwing move, but easy with a throwing copy. This fundamental fact is reflected everywhere over the Standard library.
GCC and VS treat this differently because they are in different stages of conformance. VS has left noexcept to be one of the last features they implement, so their behaviour is a kind of mid-way between C++03's behaviour and C++11/14's. Particularly, since they don't have the ability to tell if your move constructor is actually noexcept or not, they basically just have to guess. From memory they simply assume that it is noexcept because throwing move constructors are not common and not being able to move would be a critical problem.

Strange behavior of copy-initialization, doesn't call the copy-constructor!

I was reading the difference between direct-initialization and copy-initialization (§8.5/12):
T x(a); //direct-initialization
T y = a; //copy-initialization
What I understand from reading about copy-initialization is that it needs accessible & non-explicit copy-constructor, or else the program wouldn't compile. I verified it by writing the following code:
struct A
{
int i;
A(int i) : i(i) { std::cout << " A(int i)" << std::endl; }
private:
A(const A &a) { std::cout << " A(const A &)" << std::endl; }
};
int main() {
A a = 10; //error - copy-ctor is private!
}
GCC gives an error (ideone) saying:
prog.cpp:8: error: ‘A::A(const A&)’ is private
So far everything is fine, reaffirming what Herb Sutter says,
Copy initialization means the object is initialized using the copy constructor, after first calling a user-defined conversion if necessary, and is equivalent to the form "T t = u;":
After that I made the copy-ctor accessible by commenting the private keyword. Now, naturally I would expect the following to get printed:
A(const A&)
But to my surprise, it prints this instead (ideone):
A(int i)
Why?
Alright, I understand that first a temporary object of type A is created out of 10 which is int type, by using A(int i), applying the conversion rule as its needed here (§8.5/14), and then it was supposed to call copy-ctor to initialize a. But it didn't. Why?
If an implementation is permitted to eliminate the need to call copy-constructor (§8.5/14), then why is it not accepting the code when the copy-constructor is declared private? After all, its not calling it. Its like a spoiled kid who first irritatingly asks for a specific toy, and when you give him one, the specific one, he throws it away, behind your back. :|
Could this behavior be dangerous? I mean, I might do some other useful thing in the copy-ctor, but if it doesn't call it, then does it not alter the behavior of the program?
Are you asking why the compiler does the access check? 12.8/14 in C++03:
A program is ill-formed if the copy
constructor or the copy assignment
operator for an object is implicitly
used and the special member function
is not accessible
When the implementation "omits the copy construction" (permitted by 12.8/15), I don't believe this means that the copy ctor is no longer "implicitly used", it just isn't executed.
Or are you asking why the standard says that? If copy elision were an exception to this rule about the access check, your program would be well-formed in implementations that successfully perform the elision, but ill-formed in implementations that don't.
I'm pretty sure the authors would consider this a Bad Thing. Certainly it's easier to write portable code this way -- the compiler tells you if you write code that attempts to copy a non-copyable object, even if the copy happens to be elided in your implementation. I suspect that it could also inconvenience implementers to figure out whether the optimization will be successful before checking access (or to defer the access check until after the optimization is attempted), although I have no idea whether that warranted consideration.
Could this behavior be dangerous? I
mean, I might do some other useful
thing in the copy-ctor, but if it
doesn't call it, then does it not
alter the behavior of the program?
Of course it could be dangerous - side-effects in copy constructors occur if and only if the object is actually copied, and you should design them accordingly: the standard says copies can be elided, so don't put code in a copy constructor unless you're happy for it to be elided under the conditions defined in 12.8/15:
MyObject(const MyObject &other) {
std::cout << "copy " << (void*)(&other) << " to " << (void*)this << "\n"; // OK
std::cout << "object returned from function\n"; // dangerous: if the copy is
// elided then an object will be returned but you won't see the message.
}
C++ explicitly allows several optimizations involving the copy constructor that actually change the semantics of the program. (This is in contrast with most optimizations, which do not affect the semantics of the program). In particular, there are several cases where the compiler is allowed to re-use an existing object, rather than copying one, if it knows that the existing object will become unreachable. This (copy construction) is one such case; another similar case is the "return value optimization" (RVO), where if you declare the variable that holds the return value of a function, then C++ can choose to allocate that on the frame of the caller, so that it doesn't need to copy it back to the caller when the function completes.
In general, in C++, you are playing with fire if you define a copy constructor that has side effects or does anything other than just copying.
In any compiler, syntax [and semantic] analysis process are done prior to the code optimization process.
The code must be syntactically valid otherwise it won't even compile. Its only in the later phase (i.e code optimization) that the compiler decides to elide the temporary that it creates.
So you need an accessible copy c-tor.
Here you can find this (with your comment ;)):
[the standard] also says that the temporary copy
can be elided, but the semantic
constraints (eg. accessibility) of the
copy constructor still have to be
checked.
RVO and NRVO, buddy. Perfectly good case of copy ellision.
This is an optimization by the compiler.
In evaluating: A a = 10; instead of:
first constructing a temporary object through A(int);
constructing a through the copy constructor and passing in the temporary;
the compiler will simply construct a using A(int).

why does the below code compile successfully in VS2010?

I have the below exception class.
class ExceptionTest : std::exception
{
public:
ExceptionTest(int value):
m_value(value)
{
}
~ExceptionTest()
{
}
private:
ExceptionTest(const ExceptionTest& test)
{
}
int m_value;
};
I then use it in this way -
int checkexception()
{
throw ExceptionTest(2);
}
int main()
{
try
{
checkexception();
}
catch (ExceptionTest& exception)
{
cout<<"haha";
}
return 1;
}
This works perfectly fine even though the copy constructor is private.
If you catch the exception by value it fails -
int main()
{
try
{
checkexception();
}
catch (ExceptionTest exception) --> this fails
{
cout<<"haha";
}
return 1;
}
the error i get is
error C2316: 'ExceptionTest' : cannot be caught as the destructor and/or copy
constructor are inaccessible
I get a linker error if I do not define the copy constructor in the class
class ExceptionTest : std::exception
{
public:
ExceptionTest(int value):
m_value(value)
{
}
~ExceptionTest()
{
}
private:
ExceptionTest(const ExceptionTest& test);
int m_value;
};
LINK : C:\Users\sumitha\Documents\Visual Studio 2010\Projects\test\Debug\test.exe not found or not built by the last incremental link; performing full link
1>main.obj : error LNK2001: unresolved external symbol "private: __thiscall ExceptionTest::ExceptionTest(class ExceptionTest const &)" (??0ExceptionTest##AAE#ABV0##Z)
1>C:\Users\sumitha\Documents\Visual Studio 2010\Projects\test\Debug\test.exe : fatal error LNK1120: 1 unresolved externals
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
If the above is true, we can always make the copy constructor of the exception class private, so that the caller would be forced to catch the exception by reference. I think this is happening because of "return value optimization"
This appears to be an error in VS2010's C++ implementation.
When you throw an expression a temporary exception object is created by copying (or moving) the operand of the throw expression. If the expression has class type this involves the copy (or move) constructor and the constructor must be accessible at the point of the throw. If the copy constructor of an exception object is private then that object could only be thrown from a member function or a friend function.
This concern is completely independent of whether the exception object is subsequently caught by value or by reference later in the program.
In constructing the temporary exception object, the actual copy may be elided but C++ requires that the constructor that would have been used still be accessible.
ISO/IEC 14882:2003 15.1 [except.throw] / 5:
If the use of the temporary object can be eliminated without changing the meaning of the program except for the execution of constructors and destructors associated with the use of the temporary object (12.2), then the exception in the handler can be initialized directly with the argument of the throw expression. When the thrown object is a class object, and the copy constructor used to initialize the temporary copy is not accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated).
Similarly, if the destructor for that object is not accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated).
This requirement has not been removed in C++0x although the throw expression may now be moved instead of copied where appropriate.
Draft n3291 15.1 [except.throw] / 5:
When the thrown object is a class object, the copy/move constructor and the destructor shall be accessible, even if the copy/move operation is elided (12.8).
Like Marino said, if you catch by reference there is no copy construction involved.
So, the compiler doesn't look for it, and never fails on a private constructor :)
As a sidenote, I recall that specifically for exceptions there is an 'exception' (no pun intended) in the specs regarding the pass-by-value semantics. I don't know the specifics anymore, but it has to do with
passing local exceptions from an auto variable (stackallocated) which is naturally a bit tricky while unwinding the stack
perhaps active specifically in the case of a re-throw of an exception that was caught by value
.
....
catch (std::runtime_error e)
{
// ....
throw;
}
When you catch the exception using reference (& symbol) you will get the same exception object that was generated i.e the variable in the catch block will be pointing to same exception object in memory that was thrown and hence no need to have a copy cons. Where as if you use the other catch block which catches the exception by value then code needs to be generated by the compiler to create a copy of the exception object that is being caught and assign this new copy to the variable in the catch block, hence you need to have a public copy cons.
Making the copy constructor (and assignment operator) private is a very widely used technique in C++ to prevent copying. Arguably, the vast majority of C++ classes (but not exceptions, which must have an accessible copy constructor) should have copying disabled - I know that almost all of the classes in my own code do this.