Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

C++ and Beyond 2012: Andrei Alexandrescu - Systematic Error Handling in C++

Download

Right click “Save as…”

Andrei Alexandrescu presents "Systematic Error Handling in C++". This was filmed at C++ and Beyond 2012

Abstract:

Writing code that is resilient upon errors (API failures, exceptions, invalid memory access, and more) has always been a pain point in all languages. This being still largely an unsolved (and actually rather loosely-defined) problem, C++11 makes no claim of having solved it. However, C++11 is a more expressive language, and as always more expressive features can be put to good use toward devising better error-safe idioms and libraries.

This talk is a thorough visit through error resilience and how to achieve it in C++11. After a working definition, we go through a number of approaches and techniques, starting from the simplest and going all the way to file systems, storage with different performance and error profiles (think HDD vs. RAID vs. Flash vs. NAS), and more. As always, scaling up from in-process to inter-process to cross-machine to cross-datacenter entails different notions of correctness and resilience and different ways of achieving such.

To quote a classic, "one more thing"! An old acquaintance—ScopeGuard—will be present, with the note that ScopeGuard11 is much better (and much faster) than its former self.

Tune in. Learn. Thanks to Andrei, Herb and Scott for inviting C9 to film these wonderful sessions, rife with practical technical information for modern, professional C++ developers.

Get the slides.

Tags:

Follow the Discussion

  • FutureFuture

    Did Andrei publish his slides somewhere?

  • CharlesCharles Welcome Change

    @Future:Yes. I have to find a place to put them.

    EDIT: Here -> http://sdrv.ms/RXjNPR

    C

  • Very interesting subject matter, designing types and APIs that are robust even when misused (often innocently) is a real art. Keeping the feature set to the bare minimum is a something I find pays off in this regard.

    Any news Charles on Going Native 2013? Herb hinted that the conference would be running early next year.

  • CharlesCharles Welcome Change

    @dot_tom:

    No news to share yet.

    C

  • GaryGary

    In the swap function, the second time that gotHam is called with std::swap is useless as both this->gotHam and rhs.gotHam are false. (slide 21 or so.)

  • MikeMike

    1) In your slide covering swap, shouldn't you be calling the destructor of the previously held type before constructing the new type in its place?

    2) You can write is_callable using decltype and SFINAE.

    A working example can be found here: http://liveworkspace.org/code/3Tu4R0$8

  • GaryGary

    Re: swap calling the destructor.

    The general idiom of swap is not to destruct and recreate but to "move" the two objects such that if (a = c, b = d, swap(a,b) then a = d, b = c) You need to recall that not all objects once destroyed can be re-constructed.

    Yeah, you probably want to test for "is callable" to get the right error message rather than getting "construction."

  • MikeMike

    @Gary: I think you misunderstood my point, but I also didn't explain it very well.

    In the swap example, in the event that gotHam is true but rhs.gotHam is false, he is constructing a T in rhs and a std::exception_ptr in *this. However, there is currently a live std::exception_ptr in rhs so the destructor should be called before the placement new and vice-versa for *this.

    Even though he std::move'd the std::exception_ptr out of rhs, it is still a live object and the destructor needs to be called.

    This is how unrestricted unions should work.

  • GaryGary

    http://liveworkspace.org/code/3Tu4R0$8

    only tests for the things that also std::is_function (C++11 http://en.cppreference.com/w/cpp/types would already return true for.)

    http://stackoverflow.com/questions/5100015/c-metafunction-to-determine-whether-a-type-is-callable has much more indepth discussion about the problem of implementing isCallable.

  • GaryGary

    @Mike re destruction of the rhs.exception

    If std::move has moved all of the things in the rhs.exception there won't be anything left to destruct. (that's probably why his test cases don't leak.) But you are right, if the rhs.exception doesn't have move semantics and it has allocated stuff relying on it's destructor to destroy them, you'd get a copy in this->exception and leak the memory that was in rhs.exception.

    Is that the point you're trying to explain?

  • MikeMike

    @Gary: the is_callable type trait I linked to above does something different to is_function.

    is_callable takes a callable type as its first parameter and a list of types to call it with.

    I.e. is_callable<int (int, float), int, float>::value would be true but is_callable<int (), int, float>::value is false.

    This should work for functors as well as ordinary functions.

  • MikeMike

    @Gary: my point regarding calling the destructor is that, because T and std::exception_ptr are in a union, they share the same address.

    Therefore, if you use placement new to create a T at that address without first calling the destructor for std::exception_ptr you have undefined behaviour.

  • felix9felix9 the cat that walked by itself

    Oh, conclusion: D is much better Big Smile

  • MohanMohan

    VC++ / C++ teams are really adding really super useful and practically needed features! But Please do not stop adding or improving this great universal language. The pain point of exception handling addressed via this great feature. a HUGE Thank you for adding this feature and its great presentation CH9!

    Looking forward for more and more presentations on C++

  • GaryGary

    @Mike re swap expection_ptr
    "Therefore, if you use placement new to create a T at that address without first calling the destructor for std::exception_ptr you have undefined behaviour."

    Here's the code from the slide with my comments explaining what is going on.

    void swap(Expected& rhs) {
    if (gotHam) {
    if (rhs.gotHam) {
    using std::swap;
    swap(ham, rhs.ham);
    } else {
    // Gary:
    // After this next call, "t" holds the exception_ptr
    // it's no longer in the union space.
    auto t = std::move(rhs.spam);
    // Gary:
    // There is no guarantee that the space ham is built in is
    // empty but that doesn't matter, it will just overwrite
    // anything currently there.
    new(&rhs.ham) T(std::move(ham));
    // Gary:
    // here's the copy construction of spam, but it's ok,
    // because t::~exception_ptr will be called at the end of the
    // scope.
    new(&spam) std::exception_ptr(t);
    std::swap(gotHam, rhs.gotHam);
    }
    } else {
    if (rhs.gotHam) {
    rhs.swap(*this);
    // Gary:
    // ERROR? gotham == false, && rhs.gotham == true!
    // need to call swap here!
    } else {
    spam.swap(rhs.spam);
    // Gary:
    // here gotHam == false && rhs.gotHam == false
    // this line should be up where the ERROR line is.
    std::swap(gotHam, rhs.gotHam);
    }
    }
    }

    But.. exception_ptr is a shared_ptr, so if he moves it first, there is nothing that will happen by calling the destructor, it's a null-op.

    http://en.cppreference.com/w/cpp/error/exception_ptr

    Does that clarify things for you?

  • @Gary:

    // After this next call, "t" holds the exception_ptr
    // it's no longer in the union space.
    auto t = std::move(rhs.spam);

    No, it is in union space.  rhs.spam is still an exception_ptr at this point. It is until the destructor is called. t is also one.


    // There is no guarantee that the space ham is built in is
    // empty but that doesn't matter, it will just overwrite
    // anything currently there.
    new(&rhs.ham) T(std::move(ham));

    Writing over what is currently there is the opposite of "doesn't matter".  It's undefined behavior.  That always matters.

    But.. exception_ptr is a shared_ptr, so if he moves it first, there is nothing that will happen by calling the destructor, it's a null-op.

    You can't know that, period.

  • GaryGary

    @BenjaminLindley re: std::move(std::exception_ptr)

    After re-reading the draft standard I think you may be "technically correct" but in practice it doesn't matter:

    (18.8.5) Exception_ptr
    typedef unspecified exception_ptr;
    1 The type exception_ptr can be used to refer to an exception object.
    2 exception_ptr shall satisfy the requirements of NullablePointer (17.6.3.3).
    3 Two non-null values of type exception_ptr are equivalent and compare equal if and only if they refer to the same exception.
    4 The default constructor of exception_ptr produces the null value of the type.
    5 exception_ptr shall not be implicitly convertible to any arithmetic, enumeration, or pointer type.
    6 [Note: An implementation might use a reference-counted smart pointer as exception_ptr. —end note ]

    ============
    17.6.3.3 NullablePointer requirements
    1 A NullablePointer type is a pointer-like type that supports null values. A type P meets the requirements of NullablePointer if:
    — P satisfies the requirements of EqualityComparable, DefaultConstructible, CopyConstructible, CopyAssignable, and Destructible,
    — lvalues of type P are swappable (17.6.3.2), — the expressions shown in Table 25 are valid and have the indicated semantics, and — P satisfies all the other requirements of this subclause.
    2 A value-initialized object of type P produces the null value of the type. The null value shall be equivalent only to itself. A default-initialized object of type P may have an indeterminate value. [Note: Operations involving indeterminate values may cause undefined behavior. — end note ]
    3 An object p of type P can be contextually converted to bool (Clause 4). The effect shall be as if p != nullptr had been evaluated in place of p.
    4 No operation which is part of the NullablePointer requirements shall exit via an exception. 5 In Table 25, u denotes an identifier, t denotes a non-const lvalue of type P, a and b denote values of type
    (possibly const) P, and np denotes a value of type (possibly const) std::nullptr_t.
    ==========

    Which means rather than:

    auto t = std::move(rhs.spam);

    exception_ptr t; // create a null pointer
    t.swap(rhs.spam);

    now yes you should call
    rhs.spam::operator ~exception();

    but in practice, just what does happen to a "null pointer" during destruction? nothing, but as you say, we can't officially "know this."

    So I agree with you, Andre's code should call the destructor, and the compiler should optimize it away.

  • Great talk!

     

    Thanks for sharing the presentation.

  • In the destructor of Expected<T>, Andrei mentions the importance of including the following line:

    using std::exception_ptr;

     Was that necessary? I tried to replicate the scenario and was able to call the destructor without the 'using' statement.

  • GaryGary

    @shakoosh re: using std::exception_ptr;

    Andrei claimed that it wouldn't compile for him without it. (ie he tried to call the destructor with

    rhs.spam.~std::exception_ptr();

    which didn't parse. If your compiler handled it, that's great, his solution was to bring std::exception_ptr into the lookup space with the using statement and then he could write it as

    rhs.spam.~excpetion_ptr();

    as usual YMMV.

  • I have implemented scope(failure) and scope(success) in C++. https://github.com/panaseleus/stack_unwinding
    I.e. ScopeGuard without need to call .dismiss() manually.

    For example:

    try
    {
        int some_var=1; // some_var is just for example of capturing,
        // but not requirement
        cout << "Case #1: stack unwinding" << endl;
        scope(exit)
        {
            cout << "exit " << some_var << endl;
            ++some_var;
        };
        scope(failure)
        {
            cout << "failure " << some_var  << endl;
            ++some_var;
        };
        scope(success)
        {
            cout << "success " << some_var  << endl;
            ++some_var;
        };
        throw 1;
    } catch(int){}

  • viboesviboes

    The fact that you need to dismiss each one of the scoped guards is a sign that your design doesn't Scale well to multi-step transactions.

    Adding a transaction object allows to overcome this scaling problem

    {{{
    transaction tr;
    <action1>
    auto g1 = tr.on_failure([&] { <rollback1> });
    <action2>
    auto g2 = tr.on_failure([&] { <rollback2> });
    <next2>
    tr.commit();
    }}}

    In addition, adding a scoped success action becomes quite simple.

    P.S. I like the alternative approach defined in https://github.com/panaseleus/stack_unwinding even if the code is not completely portable in C++11.

  • P.S. I like the alternative approach defined in https://github.com/panaseleus/stack_unwinding even if the code is not completely portable in C++11.

    Yes, it based on top of platform-specific implementation of uncaught_exception_count.

    However, I made it work on all platforms I have access to - MSVC, GCC, Clang ( see full list at github readme ).

    And I think it should be easy to implement it on other platforms, because info about current exceptions in flight should be stored somewhere.

    By the way, at 1:18:10 Andrei mentioned that he would accept GCC only solution.

  • sellibitzesellibitze

    From the discussion reddit:

    [–]sellibitze 2 points 4 hours ago

    At slide 27 they were talking about whether it is possible to constrain the function template to only accept callables. Yes, it is. For example, with the following default template parameter and the "decl-tools".

    template <class F, class = decltype(std::declval<F>()())>
    static Expected fromCode(F fun) {
    try {
    return Expected(fun());
    } catch (...) {
    return fromException();
    }
    }

    In C++11 function template are allowed to have default template parameters. And they don't have to have a name. In case F is deduced but not a callable type, the decltype-expression won't be valid. But this is considered a substitution failure which is not an error and the template is ignored. You can even turn this into a free function template and let the return type be deduced so you don't have to type the return type manually:

    template <class F,
    class R = typename std::decay<decltype(std::declval<F>()())>::type
    >
    Expected<R> expFromCode(F fun) {
    try {
    return {fun()};
    } catch (...) {
    return Expected<R>::fromException();
    }
    }

    And instead of the static member function "fromException" ond could properly overload the constructor without ambiguity issues by some form of tag dispatching.

  • And to elaborate on sellibitze's answer this:

    template <class F,
    class R = typename std::decay<decltype(std::declval<F>()())>::type
    >
    Expected<R> expFromCode(F fun)

    can be more easily written as

    template <class F>
    auto expFromCode(F fun) -> Expected<decltype(fun())>

    I left the std::decay off because I don't see why the function's return type needs to decay.

  • Roman PerepelitsaRoman Perepelitsa

    A minor correction. declval<F> in sellibitze's post should be replaced with declval<F&> because in the body of expFromCode 'fun' is an lvalue. F may return different types if it's called on rvalue or lvalue object.

    Alternatively, expFromCode could use perfect forwarding for F.

  • Remko TronconRemko Troncon

    Is there a reason why the code doesn't include a getSpam()? E.g. when you want to get the message of the exception, or even some other custom field?

  • Johannes SchaubJohannes Schaub

    Instead of using a using declaration, you can also use an alias template to conform with the syntax (such an alias template should be part of any good library):

    template<typename T>
    using id = T;

    Now you can simply write

    Expected()
    {
    if (gotHam) ham.~T();
    else spam.~id<std::exception_ptr>();
    }

  • MikelMikel

    For "Related Work", Haskell's "Either" is closer to what you want than "Maybe".
    http://book.realworldhaskell.org/read/error-handling.html#errors.either
    http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/Data-Either.html

  • CuriousGeorgeCurious​George

    This approach tries to marry error return codes with exceptions, and I can't find a good reason why would anybody want to do it. You either need one or the other, not both. And if you use exceptions you don't want to delay throwing them by travelling through "Expected" functions, and you would throw them in the get() function anyway. That delaying and transporting of exception_ptr makes sense in an asynchronous code, but it's already a solved problem in the "std::future" class, so it looks like Expected class -- is a solution looking for a problem.

  • Ross BRoss B

    I just discovered the Scala scalaz.Validation provides a similar mechanism to Expected<T> proposed here, except that scalaz.Validation allows the failure (spam) to be any type, not just exception.

    scalaz.Validation also provides a nice way of handling error aggregation when you need to validate multiple types to construct another type: the result of validation is either the new constructed type or a non-empty list of validation errors. This would be something like:

    ExpectedNEL<D> constructD( Expected<A>& a, Expected<B>& b, Expected<C>& c );

    If a, b and c are valid then D(a,b,c) is constructed, otherwise the result is a non-empty-list ("NEL") of exceptions.

    scalaz.Validator has a nice combinator syntax for expressing the constructD functoin.



  • Ross BRoss B

    I'm told in addition to scalaz.Validation, Try[+t] in Scala 2.10 is also very closely related:

    http://www.scala-lang.org/api/current/index.html#scala.util.Try

  • Alexander KjeldaasAlexander Kjeldaas

    The focus on Haskell's "Maybe T" is misguided.

    What is used in Haskell is "Either<L,R>" which *does* include information on the error.

    Either<L,R> is either "right", or "left", "left" being erroneous :-).

    Maybe T is a simplified Either, you don't use it in high-level APIs, but you can use it in simple cases or internally in a function.

  • Qiao Zhi QiangQiao Zhi Qiang


    // build with VC++ 2010
    //auto_ptr<int> x;//error C2065: 'auto_ptr' : undeclared identifier
    std::auto_ptr<int> a;
    a.~auto_ptr();//OK, //class auto_ptr
    a.std::auto_ptr<int>::~auto_ptr();//OK

    std::exception_ptr p;
    //p.~exception_ptr();// error C2300: 'std::_Exception_ptr' : class does not have a destructor called '~exception_ptr' //typedef _Exception_ptr exception_ptr;
    p.std::exception_ptr::~exception_ptr();//OK

  • Qiao Zhi QiangQiao Zhi Qiang

    @Gary

    } else {
    if (rhs.gotHam) {
    rhs.swap(*this); //Qiao Zhi Qiang: no error, here call self and go into "if (gotHam)".

    // Gary:
    // ERROR? gotham == false, && rhs.gotham == true!
    // need to call swap here!
    } else {
    spam.swap(rhs.spam);
    // Gary:
    // here gotHam == false && rhs.gotHam == false
    // this line should be up where the ERROR line is.
    std::swap(gotHam, rhs.gotHam); //Qiao Zhi Qiang: no error, swap(false,false), this is noop.see if (gotHam) {if (rhs.gotHam).
    }
    }

  • Qiao Zhi QiangQiao Zhi Qiang

    @sellibitze,@CornedBee

    template <class F, class = decltype(std::declval<F>()())>
    static Expected fromCode(F fun) {//Qiao Zhi Qiang: Expected is Expected<T> here
    try {
    return Expected(fun());//Qiao Zhi Qiang: so fun() should return a object of T or Expected<T>, and if Fun is not a callable, this line will fail.
    } catch (...) {
    return fromException();
    }
    }

  • Szymon GatnerSzymon Gatner

    Great talk as usual. I actually also added getValueOr(const T&) to the Expected
    class (borrowed from optional::get_value_or). It returns a value or passed argument if Expected is not valid (holds exception)

  • Great talk! Hope that there will great in 2013 as well!

    Sad, that VS 2010 does not support the new unions and partial specialization of function templates. (I know that the VS 2012 CTP Nov 12 support this, but it is not a released version and so not usable in production.)

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.