Question

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?

Was it helpful?

Solution

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);

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top