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)

8
  • Init g; void somefunction(Object object){object.~Object(); new(&object)Object(g);} maybe? Commented Jul 2, 2017 at 12:27
  • 4
    Here is a much smaller example which I think demonstrates the same issue: 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." Commented Jul 2, 2017 at 12:35
  • 2
    MSVC++ applies this optimization. Some do, some don't, optimizers are not created equal. Commented Jul 2, 2017 at 12:50
  • 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. Commented Jul 2, 2017 at 13:18
  • 2
    @MarcGlisse Undefined behaviour when a const member is involved (N4659 [basic.life] ¶ 8.3) Commented Jul 2, 2017 at 17:31

2 Answers 2

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

Sign up to request clarification or add additional context in comments.

2 Comments

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.
This quote is not enough if m_initType is not an object... (the standard has a rather restrictive definition of object)
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.

6 Comments

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.
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.
@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.
@Noname that's why I said to compile with -Winline -Werror. Then it cannot compile unless inlining occurs.
@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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.