Assume the following class:
class Example
{
public:
...
Example& operator=(const Example& rhs);
...
private:
other_type *m_content;
size_t m_content_size;
}
Example& Example::operator=(const Example& rhs)
{
if (this != &rhs)
{
delete m_content;
m_content = nullptr;
m_content = getCopiedContent(rhs);
}
return *this;
}
I know that this is not the best way to implement operator=
but that's on purpose, because my question is about these two lines:
m_content = nullptr;
m_content = getCopiedContent(rhs);
Can be that the compiler will optimize out m_content = nullptr;
even though getCopiedContent
is not defined as throw()
or noexcept
:
other_type* getCopiedContent(const Example& obj);
On one hand the compiler may assume that if right after m_content = nullptr;
I overwrite the value of m_content
with the return value of getCopiedContent
, it may optimize out the whole m_content = nullptr;
expression. On the other hand if the compiler optimizes it out and getCopiedContent
throws an exception, m_content
will contain a non valid value.
Does C++ standard state anything regarding such scenario?
If an exception is not supposed to be thrown, the program cannot be assumed to cope with the error and should be terminated as soon as possible. Declaring a function noexcept helps optimizers by reducing the number of alternative execution paths. It also speeds up the exit after failure.
noexcept means that a function will not throw, not that it cannot throw, and the penalty for failure to comply is calling std::terminate , not UB.
noexcept is primarily used to allow "you" to detect at compile-time if a function can throw an exception. Remember: most compilers don't emit special code for exceptions unless it actually throws something.
C26440 DECLARE_NOEXCEPT "Function can be declared 'noexcept'." If code is not supposed to cause any exceptions, it should be marked as such by using the 'noexcept' specifier. This would help to simplify error handling on the client code side, as well as enable compiler to do additional optimizations.
Can be that the compiler will optimize out m_content = nullptr; even though getCopiedContent is not defined as throw() or noexcept:
Yes. This is a redundant operation with no side-effects. Any self-respecting compiler will optimise the redundant store away. In fact you'll have to work really hard to keep the redundant store from being optimised out, such as:
std::atomic
(if it's atomic, writes are obliged to to transmitted to other threads)volatile
std::mutex
) for the same reasons as (1)On the other hand if the compiler optimizes it out and getCopiedContent throws an exception, m_content will contain a non valid value
Good observation. The compiler is permitted to perform the write of nullptr
in the exception handler. i.e. it may re-order instructions in order to save operations provided the total outcome was 'as if' it did not.
Does C++ standard states anything regarding such scenario?
Yes. It has the 'as-if' rule. While reasoning about one thread, the visible outcome must be 'as-if' each of your statements were executed sequentially with no optimisations against a non-pipelined, non-cached, very simple memory model. Note that no computer produced in the past 20 years is actually this simple, but the outcome of the program must be as if it were.
There is one exception to this - copy elision. Side effects of eliding redundant copies under certain circumstances do not need to be preserved. For example, while eliding copies of arguments that are temporaries and during RVO.
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