Zijn functie-retourwaarden automatische objecten en dus gegarandeerd vernietigd?

In [behalve.ctor] de standaard (N4140) garandeert dat:

…destructors worden aangeroepen voor
alle automatische objecten geconstrueerd sinds het try-blok was
ingevoerd…

In het volgende voorbeeld bewijst de lege uitvoerdat de geretourneerde waarde van functie foois niet vernietigd, hoewel het is gebouwd. Samengesteld met g++ (5.2.1) en clang++ (3.6.2-1) en met opties -O0 -fno-elide-constructors -std=c++14.

struct A { ~A() { cout << "~A\n"; } };
struct B { ~B() noexcept(false) { throw 0; } };
A foo() {
  B b;
  return {};
}
int main() {
  try { foo(); }
  catch (...) { }
}

Is dit een fout in zowel g++ als clang++, of zijn functieretourwaarden dat niet
beschouwd als automatische objecten, of is het een maas in de wet in de C++-taal?

In geen van [stmt.return], [expr.call] of [dcl.fct] heb ik kunnen vinden
een duidelijke verklaring of een functieretourwaarde als een automatische wordt beschouwd
object. De dichtstbijzijnde hints die ik heb gevonden zijn 6.3.3 p2:

…Een return-statement kan
de constructie en het kopiëren of verplaatsen van een tijdelijk object betrekken…

en 5.2.2 p10:

Een functie-aanroep is een lvalue als het resultaattype een lvalue is
referentietype of een rvalue verwijzing naar het functietype, een xwaarde als de
resultaattype is een rvalue verwijzing naar het objecttype, en anders een prwaarde.


Antwoord 1, autoriteit 100%

Functie-retourwaarden worden als tijdelijk beschouwd en de constructie van de retourwaarde wordt gesequenced voordat de lokale bevolking wordt vernietigd.

Helaas is dit onvoldoende gespecificeerd in de standaard. Er is een open defectdie dit beschrijft en biedt enige formulering om het probleem op te lossen

[…] Een return-statement met een operand van het type void mag alleen worden gebruikt in een functie waarvan het retourtype cv void is. Een return-statement met een andere operand mag alleen worden gebruikt in een functie waarvan het retourtype niet cv void is; de return-instructie initialiseert het object of de verwijzing die moet worden geretourneerd door initialisatie van het kopiëren (8.5 [dcl.init]) van de operand. […]

De initialisatie van het kopiëren van de geretourneerde entiteit wordt gesequenced vóór de vernietiging van tijdelijke bestanden aan het einde van de volledige expressie die is ingesteld door de operand van de return-instructie, die op zijn beurt wordt gesequenced vóór de vernietiging van lokale variabelen (6.6 [stmt.jump]) van het blok dat de return-instructie omsluit.

Omdat functieretourwaarden tijdelijk zijn, vallen ze niet onder de destructors are invoked for all automatic objectsaan het begin van je bericht. [class.temporary]/3zegt echter:

[…] Tijdelijke objecten worden vernietigd als de laatste stap bij het evalueren van de volledige expressie die (lexicaal) het punt bevat waar ze zijn gemaakt. Dit is waar, zelfs als die evaluatie eindigt in het genereren van een uitzondering. […]

Dus ik denk dat je dit als een bug in GCC en Clang kunt beschouwen.

Gooi niet van vernietigers 😉


Antwoord 2, autoriteit 16%

Dit is een fout, en voor één keer heeft MSVC het goed: het drukt “~A” af.


Antwoord 3, autoriteit 16%

Ik heb je code aangepast en ik denk dat we nu uit de uitvoer kunnen zien dat A niet is vernietigd.

#include<iostream>
using namespace std;
struct A {
    ~A() { cout << "~A\n"; }
    A() { cout << "A()"; }
};
struct B {
    ~B() noexcept( false ) { cout << "~B\n"; throw(0); }
    B() { cout << "B()"; }
};
A foo() {
    B b;
    return;
}
int main() {
    try { foo(); }
    catch (...) {}
}

En de uitvoer is:

B()A()~B

Dus ja, het kan een bug zijn.

Other episodes