Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clang modifies return value in destructor?

Tags:

c++

clang++

While trying to write a class that times the duration between calling it's constructor and destructor, I ran into what I think is a bug in clang. (Edit: it's not a bug; it's implementation defined copy elision)

The timer struct below keeps a pointer to the duration object that's passed in as reference and adds the duration of the scope to this.

#include <iostream>
#include <chrono>
struct timer {
    using clock      = std::chrono::high_resolution_clock;
    using time_point = clock::time_point;
    using duration   = clock::duration;
    duration* d_;
    time_point start_;
    timer(duration &d) : d_(&d), start_(clock::now()) {}
    ~timer(){
        auto duration = clock::now() - start_;
        *d_ += duration;
        std::cerr << "duration: " << duration.count() << std::endl;
    }
};

timer::duration f(){
    timer::duration d{};
    timer _(d);
    std::cerr << "some heavy calculation here" << std::endl;
    return d;
}

int main(){
    std::cout << "function: " << f().count() << std::endl;
}

When compiling this with clang 7.0.0, the output is:

some heavy calculation here
duration: 21642
function: 21642

while for g++ 8 the output is

some heavy calculation here
duration: 89747
function: 0

In this case I do like clangs behavior, but from what I've found elsewhere the return value is supposed to be copied before destructors are run.

Is this a bug with Clang? Or does this depend on (implementation defined?) return value optimization?

The behavior is the same independent of whether the duration d in timer is a pointer or a reference.

--

I do realise that the compiler inconsistency can be solved by changing f so that the scope of the timer ends before the return, but that's beside the point here.

timer::duration f(){
    timer::duration d{};
    {
        timer _(d);
        std::cerr << "some heavy calculation here" << std::endl;
    }
    return d;
}
like image 474
Ragnar Avatar asked Feb 07 '19 01:02

Ragnar


1 Answers

Short answer: due to NRVO, the output of the program may be either 0 or the actual duration. Both are valid.


For background, see first:

  • in C++ which happens first, the copy of a return object or local object's destructors?
  • What are copy elision and return value optimization?

Guideline:

  • Avoid destructors that modify return values.

For example, when we see the following pattern:

T f() {
    T ret;
    A a(ret);   // or similar
    return ret;
}

We need to ask ourselves: does A::~A() modify our return value somehow? If yes, then our program most likely has a bug.

For example:

  • A type that prints the return value on destruction is fine.
  • A type that computes the return value on destruction is not fine.
like image 119
Acorn Avatar answered Sep 28 '22 05:09

Acorn