12

Look at this code:

struct Data {
};

struct Init {
    Data *m_data;

    Init() : m_data(new Data) { }
    ~Init() {
        delete m_data;
    }
};

class Object {
    private:
        const int m_initType;
        Data *m_data;
    public:
        Object(const Init &init) : m_initType(0), m_data(init.m_data) { }
        Object(Init &&init) : m_initType(1), m_data(init.m_data) { init.m_data = nullptr; }
        ~Object() {
            if (m_initType==1) {
                delete m_data;
            }
        }
};

void somefunction(const Object &object); // it is intentionally not defined

void callInitA() {
        Init x;
        somefunction(x);
}

void callInitB() {
        somefunction(Init());
}

As Object::m_initType is const, it doesn't change after constructor. So, in theory, in callInitA, and in callInitB, the compiler knows of the value of m_initType when it inlines ~Object(). However, both gcc and clang fails to apply this optimization, and both checks the value of m_initType.

Why is that? Is there some language rule against this optimization, or compilers just don't do this kind of optimization?

(This question is closely related to this, but it is a more specific question, I hope I can get an answer for this)

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
geza
  • 28,403
  • 6
  • 61
  • 135
  • `Init g; void somefunction(Object object){object.~Object(); new(&object)Object(g);}` maybe? – Marc Glisse Jul 02 '17 at 12:27
  • 4
    Here is a much smaller example which I think demonstrates the same issue: https://godbolt.org/g/zTyctM - if you comment out `somefunction` then the whole thing is optimized out, but with `somefunction` being called the compiler generates a check for something which "can't happen." – John Zwinck Jul 02 '17 at 12:35
  • 2
    MSVC++ applies this optimization. Some do, some don't, optimizers are not created equal. – Hans Passant Jul 02 '17 at 12:50
  • @HansPassant: I tried multiple versions of ICC, GCC and Clang (using the link in my previous comment), and none of them optimized this. So, +1 for MSVC I think. – John Zwinck Jul 02 '17 at 12:58
  • 1
    @HansPassant: I've checked MSVC++, and as I see, it works a little bit differently. In MSVC++, for objects passed as value, it is the called function's reponsibility to call the destructor. If you check out John's example (thanks John!), MSVC++ fails to optimize too. – geza Jul 02 '17 at 13:18
  • 2
    @MarcGlisse Undefined behaviour when a `const` member is involved (N4659 [basic.life] ¶ 8.3) – Oktalist Jul 02 '17 at 17:31
  • @Oktalist isn't that what [`std::launder`](http://en.cppreference.com/w/cpp/utility/launder) is about? – Quentin Jul 03 '17 at 08:22
  • @Quentin Yes. If `callInitA` were `somefunction(*launder(&x))` it would be well-defined and the optimization could not be applied. It forces the compiler to discard the knowledge that `x` refers to the same `x` constructed on the previous line. – Oktalist Jul 03 '17 at 19:56

2 Answers2

3

To answer whether there is any rules in the language that forbids this kind of optimization, here's my take

From [dcl.type.cv]

Except that any class member declared mutable can be modified, any attempt to modify a const object during its lifetime results in undefined behavior.

And so in theory, the optimizer may safely assume m_initType will never change after initialization. This can of course be used to deduce whether the branch in ~Object will be taken at compile time.

That said, optimizers are free to do anything as long as the observed behaviour stay the same, so too are they free to ignore const. To make matters more complicated for the optimizer, there is an forward declared but not defined function in the mix, the optimizer probably just gave up after that to do anything useful with the information.

Comparison of defined vs undefined function

If the function is defined later on, gcc and clang both optimizes everything away. Note however, in this particular case, they will still do this even without any const.

This post might be of interest

Passer By
  • 19,325
  • 6
  • 49
  • 96
  • 1
    Thank for the answer. Yep, if the definition is visible to the compiler, it can do the usual optimization. I've edited my question to make it clear that `somefunction` is intentionally not defined. – geza Jul 02 '17 at 14:06
  • This quote is not enough if `m_initType` is not an object... (the standard has a rather restrictive definition of object) – Marc Glisse Jul 02 '17 at 20:21
0

Object destructor is not inlined in your example and you have 2 invocations, where in one m_initType is 1 and in another is 0. So compiler have to support both versions. Also, I suppose that your actual code is somewhat more complex that your example, so compiler might decide that inline whole destructor code is more expensive than keeping generic version with single 'if' inside.

Noob
  • 335
  • 1
  • 8
  • You can write `inline` in front of the destructor and compile with `g++ -O3 -Winline -Werror` - I think you will find it doesn't change anything. – John Zwinck Jul 02 '17 at 13:54
  • 1
    I think you've misunderstood my question. Object destructor **is** inlined, as it can be seen from the disassembly. My code is more complex, of course, but this missing optimization do happen in the code I posted. – geza Jul 02 '17 at 14:09
  • @JohnZwinck I can't guess what was implied. I only replied to whatever I see in question :) optimization is something voluntary and is not enforced by standard. Same for 'inline' - you can write as much as you want, but according to standard it is only recommendation. Hence compiler is free to ignore 'inline' if it doesn't see any benefit. – Noob Jul 02 '17 at 14:52
  • @Noname that's why I said to compile with -Winline -Werror. Then it cannot compile unless inlining occurs. – John Zwinck Jul 03 '17 at 01:13
  • @Noname What you say in your comment is more accurate than what you say in your answer. The compiler need not inline it, but that's not the same thing as *does not* inline it. – user207421 Jul 03 '17 at 06:11
  • @EJP In original question the destructor is NOT marked for inlining. Nowhere mentioned that code compiled with forced inlining. Therefor what I wrote in my answer is precise. – Noob Jul 17 '17 at 14:44