2016-05-11 8 views
10

Mentre il porting del codice C++ da Microsoft Visual Studio per gcc, mi sono imbattuto in uno strano bug, che alla fine ho riassunta in questo modo:distruttore di un argomento di una funzione viene chiamata in modo diverso in GCC e MSVC

#include <iostream> 
using namespace std; 

class Foo { 
public: 
    int data; 
    Foo(int i) : data(i) 
    { 
     cout << "Foo constructed with " << i << endl; 
    } 
    Foo(const Foo& f) : data(f.data) 
    { 
     cout << "copy ctor " << endl; 
    } 
    Foo(const Foo&& f) : data(f.data) 
    { 
     cout << "move ctor" << endl; 
    } 
    ~Foo() 
    { 
     cout << "Foo destructed with " << data << endl; 
    } 
}; 

int Bar(Foo f) 
{ 
    cout << "f.data = " << f.data << endl; 
    return f.data * 2; 
} 

int main() 
{ 
    Foo f1(10); 
    Foo f2(Bar(std::move(f1))); 
} 

Se I compilare ed eseguire il codice precedente con Microsoft Visual Studio 2015 Community, ottengo il seguente output:

Foo constructed with 10 
move ctor 
f.data = 10 
Foo destructed with 10 
Foo constructed with 20 
Foo destructed with 20 
Foo destructed with 10 

Tuttavia, se compilare ed eseguire il codice con gcc 6.1.1 e --std = C++ 14 , Ottengo questo risultato:

Foo constructed with 10 
move ctor 
f.data = 10 
Foo constructed with 20 
Foo destructed with 10 
Foo destructed with 20 
Foo destructed with 10 

gcc chiama il distruttore del f, l'argomento di Bar(), dopo Bar() rendimenti, mentre msvc chiama il distruttore (apparentemente) prima che ritorni, o almeno prima il costruttore di f2. Quando si suppone che lo f venga distrutto, secondo lo standard C++?

+0

'f', essendo un argomento con nome di' Bar() ', non è un temporaneo. In realtà, non vedo alcun temporaneo. Gli altri due 'Foo's sono chiamati f1 e f2. Non sono sicuro se c'è una domanda reale qui. – MSalters

+0

Probabilmente sono confuso su cosa sia un temporaneo. La mia domanda è quando l'argomento 'f' di' Bar() 'dovrebbe essere distrutto, e quale implementazione, msvc o gcc, è corretta? –

+0

@MSalters Non vedo come questo sia un duplicato di quella domanda. Riguarda l'ordine di costruzione/distruzione in 'f (a, b, c)' ma questa domanda non chiama mai una funzione con più di 1 argomento –

risposta

8

Sono a posto; dipende. Sembra sottodimensionato nello standard.

Da [expr.call]/4 (questa formulazione torna a C++ 98);

La durata di un parametro termina quando viene restituita la funzione in cui è definita. L'inizializzazione e la distruzione di ciascun parametro si verificano nel contesto della funzione di chiamata.

E CWG#1880;

WG ha deciso di rendere non specificato se gli oggetti parametro vengono distrutti immediatamente dopo la chiamata o alla fine dell'espressione completa a cui appartiene la chiamata.

Sia il comportamento di g ++ (e clang) che di MSVC sarebbe corretto, le implementazioni sono libere di scegliere un approccio rispetto all'altro.

Detto questo, se il codice che hai dipende da questo ordine, lo cambierei in modo tale che l'ordine sia più deterministico - come hai visto, porta a bug sottili.


Un esempio semplificato di questo comportamento è;

#include <iostream> 
struct Arg { 
    Arg() {std::cout << 'c';} 
    ~Arg() {std::cout << 'd';} 
    Arg(Arg const&) {std::cout << 'a';} 
    Arg(Arg&&) {std::cout << 'b';} 
    Arg& operator=(Arg const&) {std::cout << 'e'; return *this;} 
    Arg& operator=(Arg&&) {std::cout << 'f'; return *this;} 
}; 
void func(Arg) {} 
int main() { 
    (func(Arg{}), std::cout << 'X'); 
    std::cout << std::endl; 
} 

Clang e g ++ entrambi producono cXd e MSVC produce cdX.