10

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?

Alex Lop.
  • 6,810
  • 1
  • 26
  • 45
  • 2
    Long story short: You may assume your code behaves exactly as it would without any optimization. The only exceptions to this are RVO and UB. – Baum mit Augen Nov 09 '15 at 14:07

2 Answers2

5

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:

  1. make it std::atomic (if it's atomic, writes are obliged to to transmitted to other threads)
  2. make it volatile
  3. surround the write with some kind of memory barrier (e.g. lock a 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.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • *"Yes. This is a redundant operation with no side-effects."* No it's not, OP explained why it isn't. If `getCopiedContent` throws one can observe whether or not it was done, so it must be done. – Baum mit Augen Nov 09 '15 at 13:53
  • that scenario was covered in the second part of the narrative. – Richard Hodges Nov 09 '15 at 13:54
  • Then why did you write "Yes" as the first word in your answer? That is clearly wrong. At least write *"It may be optimized to be done only `getCopiedContent` actually does not throw."* or something like this, the part about doing it in the exception handler would be fine afaics. – Baum mit Augen Nov 09 '15 at 13:55
  • Forgive me, but the question was "Can be that the compiler will optimize out m_content = nullptr; even though getCopiedContent is not defined as throw() or noexcept:". While there are some caveats, the answer is "yes". As ever, the obligation is on the compiler to prove that there will be no observable side effects. If (for example) it has sight of the code in `getCopiedContent` it can prove that whether or not the function is declared to be exception-free. – Richard Hodges Nov 09 '15 at 13:59
  • So if I understood your answer correctly, the compiler may perform some reordering which will optimize the main line (e.i. moving the `m_content = nullptr;` to an exception handler) but **it is guaranteed** that `m_content` will not contain a non valid value. Did I get it correct? If so, where exactly in the exception handler will it place `m_content = nullptr;`? What if the relevant `try-catch` is in a different compilation unit? – Alex Lop. Nov 09 '15 at 14:03
  • 1
    But in general, the compiler cannot prove that an arbitrary function is `noexcept`, and in this general case, the assignment may *not* be optimized away. It may be moved elsewhere (like in the exception handler), but it will not be removed. That contradicts a simple *"Yes. This is a redundant operation with no side-effects."*. – Baum mit Augen Nov 09 '15 at 14:05
  • @RichardHodges, there is a forth way to force redundant store - call any opaque function which potentially can have access to this variable in between two stores. In our case, this would be a member function of the class, defined in a different unit of translation. – SergeyA Nov 09 '15 at 14:29
  • True. The compiler is required to be conservative in the case of aliasing – Richard Hodges Nov 09 '15 at 15:26
0

I believe this is called Dead Store Elimination.

I don't know if compiler optimizations are included in the standards except for the as-if rule.

Here is the code for Clang for the dead store elimination. http://www.llvm.org/docs/doxygen/html/DeadStoreElimination_8cpp_source.html This will only do block local ones.

Maybe there are some tools that can inline the function and do the analysis as block local to see if that nullptr store can be eliminated. Obviously, a throw in the function somewhere would make the analysis keep the nullptr store.

Obviously, the scenario you described violates the as-if rule so that is not standard compliant.

Mochan
  • 84
  • 1
  • 5