user2138149
user2138149

Reputation: 16847

C++ why is noexcept required in the context of Move Constructors and Move Assignment Operators to enable optimizations?

Consider the following class, with a move constructor and move assignment operator:

class my_class
{

    protected:

    double *my_data;
    uint64_t my_data_length;
}

my_class(my_class&& other) noexcept : my_data_length{other.my_data_length}, my_data{other.my_data}
{
    // Steal the data
    other.my_data = nullptr;
    other.my_data_length = 0;
}

const my_class& operator=(my_class&& other) noexcept
{
    // Steal the data
    std::swap(my_data_length, other.my_data_length);
    std::swap(my_data, other.my_data);

    return *this;
}

What is the purpose of noexcept here? I know that is hits to the compiler that no exceptions should be thrown by the following function, but how does this enable compiler optimizations?

Upvotes: 6

Views: 4947

Answers (3)

Jonathan Wakely
Jonathan Wakely

Reputation: 171303

The special importance of noexcept on move constructors and assignment operators is explained in detail in https://vimeo.com/channels/ndc2014/97337253

Basically, it doesn't enable "optimisations" in the traditional sense of allowing the compiler to generate better code. Instead it allows other types, such as containers in the library, to take a different code path when they can detect that moving the element types will never throw. That can enable taking an alternate code path that would not be safe if they could throw (e.g. because it would prevent the container from meeting exception-safety guarantees).

For example, when you do push_back(t) on a vector, if the vector is full (size() == capacity()) then it needs to allocate a new block of memory and copy all the existing elements into the new memory. If copying any of the elements throws an exception then the library just destroys all the elements it created in the new storage and deallocates the new memory, leaving the original vector is unchanged (thus meeting the strong exception-safety guarantee). It would be faster to move the existing elements to the new storage, but if moving could throw then any already-moved elements would have been altered already and meeting the strong guarantee would not be possible, so the library will only try to move them when it knows that can't throw, which it can only know if they are noexcept.

Upvotes: 11

Klaus
Klaus

Reputation: 25613

First of all I would remark that in move constructors or move assignment nothing should throw and there seems to be no need to this ever. The only thing which must be done in constructors/assignment operator is dealing with already allocated memory and pointers to them. Normally you should not call any other methods which can throw and your own moving inside your constructor/operator has no need to do so. But on the other hand a simple output of a debug message breaks this rule.

Optimization can be done in a some different ways. Automatically by the compiler and also by different implementations of code which uses your constructors and assignment operator. Take a look to the STL, there are some specializations for code which are different if you use exceptions or not which are implemented via type traits.

The compiler itself can optimize better while having the guarantee that any code did never throw. The compiler have a guaranteed call tree through your code which can be better inlined, compile time calculated or what so ever. The minimum optimization which can be done is to not store all the informations about the actual stack frame which is needed to handle the throw condition, like deallocation variables on the stack and other things.

There was also a question here: noexcept, stack unwinding and performance

Maybe your question is a duplicate to that?

A maybe helpful question related to this I found here: Are move constructors required to be noexcept? This discuss the need of throwing in move operations.

What is the purpose of noexcept here?

At minimum saving some program space, which is not only relevant to move operations but for all functions. And if your class is used with STL containers or algorithms it can handled different which can result in better optimization if your STL implementation uses these informations. And maybe the compiler is able to get better general optimization because of a known call tree if all other things are compile time constant.

Upvotes: 0

Elohim Meth
Elohim Meth

Reputation: 1827

IMHO using noexcept will not enable any compiler optimization on its own. There are traits in STL:

std::is_nothrow_move_constructible
std::is_nothrow_move_assignable

STL containters like vector etc use these traits to test type T and use move constructors and assignment instead of copy constructors and assignment.

Why STL use these traits instead of:

std::is_move_constructible
std::is_move_assignable

Answer: to provide strong exception guarantee.

Upvotes: 1

Related Questions