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 std::string
copy constructor can throw an exception if memory allocation fails, and if an exception is thrown before the currently processed exception is caught, the program is terminated.
However, does it still hold in the world of move constructors? Won't the move constructor be used instead of the copy constructor when throwing an exception? Do I understand correctly that with C++11 no memory allocation will take place, no chance of exception exists and std::string
is absolutely fine in exception classes now?
std::move() will cast your variable to an rvalue, allowing it to bind to a move constructor. This also means that you can pass an rvalue to a function that uses pass-by-value.
If a copy constructor, copy-assignment operator, move constructor, move-assignment operator, or destructor is explicitly declared, then: No move constructor is automatically generated. No move-assignment operator is automatically generated.
std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.
std::move is a cast. It takes any value as argument and returns that same value in the xvalue category. And a value of type T and category xvalue is denoted thus: T&& . The move operation itself is performed by one of the constructors of the object to which it moves.
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>{}, "");
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With