Does rule of not embedding std::string in exceptions still hold with move constructors?

前端 未结 2 1034
挽巷
挽巷 2021-02-05 14:46

I heard some time ago that I should not create exception classes which would have fields of std::string type. That\'s what Boost website says. The rationale is that

相关标签:
2条回答
  • 2021-02-05 15:13

    Whether a copy of the string is made when an exception is thrown depends on the catch block.

    Take this example:

    #include <iostream>
    
    struct A
    {
    };
    
    void foo()
    {
       throw A();
    }
    
    void test1()
    {
       try
       {
          foo();
       }
       catch (A&& a)
       {
       }
    }
    
    void test2()
    {
       try
       {
          foo();
       }
       catch (A const& a)
       {
       }
    }
    
    void test3()
    {
       try
       {
          foo();
       }
       catch (A a)
       {
       }
    }
    
    int main()
    {
       test1();
       test2();
       test3();
    }
    

    You will not make a copy of A in test1 or test2 but you will end up making a copy in test3.

    0 讨论(0)
  • 2021-02-05 15:22

    The answer is:

    Yes, you still don't want to embed a std::string into your exception types. Exceptions are often copied, sometimes without your knowledge. For example, on some platforms std::rethrow_exception will copy the exception (and on some it won't).

    For best practice, keep your copy constructor noexcept.

    However all is not lost. A little known fact is that C++ has always had within the standard an immutable ref-counted string type (with a non-throwing copy constructor), just with an obfuscated name. Two names actually:

    logic_error
    runtime_error
    

    The specs for these types are such that they must contain an immutable ref-counted string-like object. Well, not completely immutable. You can replace the string with an assignment. But you can't otherwise modify the string in place.

    My advice is to either derive from one of these types, or if that is not acceptable, embed one of these types and treat it as an immutable ref-counted string type:

    #include <stdexcept>
    #include <iostream>
    
    class error1
        : public std::runtime_error
    {
        using msg_ = std::runtime_error;
    public:
        explicit error1(std::string const& msg)
            : msg_(msg)
        {}
    };
    
    class error2
    {
        std::runtime_error msg_;
    public:
        explicit error2(std::string const& msg)
            : msg_(msg)
        {}
    
        char const* what() const noexcept {return msg_.what();}
    };
    
    void
    test_error1()
    {
        try
        {
            throw error1("test1");
        }
        catch (error1 const& e)
        {
            std::cout << e.what() << '\n';
        }
    }
    
    void
    test_error2()
    {
        try
        {
            throw error2("test2");
        }
        catch (error2 const& e)
        {
            std::cout << e.what() << '\n';
        }
    }
    
    int
    main()
    {
        test_error1();
        test_error2();
    }
    

    The std::lib will take care of all the string-handling and memory management for you, and you get noexcept copying in the bargain:

    static_assert(std::is_nothrow_copy_constructible<error1>{}, "");
    static_assert(std::is_nothrow_copy_assignable   <error1>{}, "");
    static_assert(std::is_nothrow_copy_constructible<error2>{}, "");
    static_assert(std::is_nothrow_copy_assignable   <error2>{}, "");
    
    0 讨论(0)
提交回复
热议问题