5

I've been playing around with the Compiler Explorer recently. I loaded one of their examples that takes pointer parameters and changed it to instead take unique_ptr parameters. But I noticed that in the output assembly, calls to operator delete were conspicuously absent. I'm curious if anyone knows why.

Here's an example you can paste into the explorer. Be sure to also put -O3 in the compiler options.

#include <memory>

using std::unique_ptr;

void maxArray(unique_ptr<double[]> x, unique_ptr<double[]> y) {
    for (int i = 0; i < 65536; i++) {
        if (y[i] > x[i]) x[i] = y[i];
    }
}

EDIT: Also for comparison, if instead I paste in one of the code examples from cppreference, then I do get operator delete in the output.

#include <iostream>
#include <memory>

struct Foo
{
    Foo()      { std::cout << "Foo::Foo\n";  }
    ~Foo()     { std::cout << "Foo::~Foo\n"; }
    void bar() { std::cout << "Foo::bar\n";  }
};

void f(const Foo &)
{
    std::cout << "f(const Foo&)\n";
}

int main()
{
    std::unique_ptr<Foo> p1(new Foo);  // p1 owns Foo
    if (p1) p1->bar();

    {
        std::unique_ptr<Foo> p2(std::move(p1));  // now p2 owns Foo
        f(*p2);

        p1 = std::move(p2);  // ownership returns to p1
        std::cout << "destroying p2...\n";
    }

    if (p1) p1->bar();

    // Foo instance is destroyed when p1 goes out of scope
}

EDIT: +1 to krzaq. It's the caller, not the callee, that constructs and destroys parameters.

Olsonist
  • 2,051
  • 1
  • 20
  • 35
Jeff M
  • 2,492
  • 3
  • 22
  • 38

1 Answers1

6

This problem boils down to:

void foo(unique_ptr<int>)
{
}

foo(make_unique<int>());

Where does foo's parameter live?

The following standardese is pertinent to this question:

N4140 §5.2.2 [expr.call]/4

The lifetime of a parameter ends when the function in which it is defined returns. The initialization and destruction of each parameter occurs within the context of the calling function.

In other words: your maxArray is not required to be responsible for calling x and y's destructors; gcc implements it this way, and that's why there are no delete calls in the codegen.

krzaq
  • 16,240
  • 4
  • 46
  • 61
  • 1
    "The initialization and destruction of each parameter occurs within the context of the calling function." doesn't mean that the caller is responsible for destruction. (To see what it actually means, read the rest of the paragraph you quoted). g++ actually doesn't comply with the first sentence you quoted. It delays destruction of parameters until the end of the full-expression doing the call, whereas that sentence says they must be destroyed before the function returning is complete. The C++17 standard is going to change this section to make that behaviour legal. – M.M Oct 16 '16 at 04:50
  • @M.M The rest of the paragraph is examples or deals with virtual functions. But I see the changed wording in C++17 draft – krzaq Oct 16 '16 at 04:54
  • 1
    I'm referring to the Example – M.M Oct 16 '16 at 04:55
  • Aren't examples, strictly speaking, "non-normative"? Anyway I'm not sure how to understand *the access of the constructor, conversion functions or destructor is checked at the point of call in the calling function.* What exactly is *access* here? – krzaq Oct 16 '16 at 04:56
  • 1
    Yes it's non-normative but it's meant to clarify what the meaning of the related text was. "access" refers to public, privte and protected checking. For example, the calling function might be a friend of the parameter class – M.M Oct 16 '16 at 05:00
  • @M.M Okay. After reading it again I think I understand it better. So, the current wording means that the parameter's lifetime ends "at the same time" as the called function returns, not at the `;` in the call-site. This makes generating destructor codegen in both places just as valid - if it's immediate (and I believe it is in the examples used). Am I correct here? – krzaq Oct 16 '16 at 05:06
  • @M.M thank you for helping me understand this :) I edited the answer, hopefully it's up to par now. – krzaq Oct 16 '16 at 05:31