Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't std::exception have a move constructor?

The best practice regarding exceptions in C++ seems to be throw by value, catch by reference. I had a look at <exception> and cppreference and I see that std::exception has a copy-constructor but no move-constructor, why is this? Wouldn't having a move-constructor allow cheaply catching by value and thus simplifying the guidelines?

like image 784
Motti Avatar asked Jul 15 '20 06:07

Motti


People also ask

Does STD move move constructor?

std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.

Is move constructor automatically generated?

No move constructor is automatically generated.

What is a move constructor C++?

A move constructor allows the resources owned by an rvalue object to be moved into an lvalue without creating its copy. An rvalue is an expression that does not have any memory address, and an lvalue is an expression with a memory address.


Video Answer


2 Answers

The descendants of std::exception do own data. For example std::runtime_error owns its what() message. And that message is dynamically allocated because it can be an arbitrarily long message.

However, the copy constructor is marked noexcept (implicitly) because the std::exception copy constructor is noexcept.

#include <stdexcept>
#include <type_traits>

int
main()
{
    static_assert(std::is_nothrow_copy_constructible<std::runtime_error>{});
}

The only way for a class to own a dynamically allocated message, and have a noexcept copy constructor, is for that ownership to be shared (reference counted). So std::runtime_error is essentially a const, reference counted string.

There was simply no motivation to give these types a move constructor because the copy constructor is not only already very fast, but the exceptional path of program is only executed in exceptional circumstances. About the only thing a move constructor for std::runtime_error could do is eliminate an atomic increment/decrement. And no one cared.

Wouldn't having a move-constructor allow cheaply catching by value and thus simplifying the guidelines?

You can already cheaply catch by value. But the guideline exists because exceptions are often part of an inheritance hierarchy, and catching by value would slice the exception:

#include <exception>
#include <iostream>
#include <stdexcept>

int
main()
{
    try
    {
        throw std::runtime_error("my message");
    }
    catch (std::exception e)
    {
        std::cout << e.what() << '\n';
    }
}

Output (for me):

std::exception
like image 141
Howard Hinnant Avatar answered Oct 17 '22 00:10

Howard Hinnant


I checked on a couple of compilers and apparently the try/catch mechanism doesn't use move semantics.

#include <iostream>
using namespace std;

struct err {
    err() { cout << "created err" << endl; }
    err(const err&) { cout << "copied err" << endl; } // Compilation error if this is commented out
    err(err&&) noexcept { cout << "moved err" << endl; }
    ~err() { cout << "destroyed err" << endl; }
};

void foo() {
    throw err{};
}

int main() {
    try {
        cout << "calling foo" << endl;
        foo();
        cout << "called foo" << endl;
    }
    catch (err e) {
        cout << "caught err" << endl;
    }
}

Output:

calling foo
created err
copied err
caught err
destroyed err
destroyed err

So having a move constructor would be meaningless.

Why this is so is probably a matter for another question :)

like image 1
Motti Avatar answered Oct 16 '22 22:10

Motti