2013-06-17 5 views
5
#include<iostream> 
using namespace std; 
class X 
{ 
    int i; 

    public: 
    X(int a=0) : i(a) {} 

    friend X operator+ (const X& left,const X&right); 

}; 
X operator+ (const X& left,const X&right) // Method 1 
{ 
    return X(left.i + right.i); 
} 

X operator+ (const X& left,const X&right) // Method 2 
{ 
    X temp(left.i + right.i); 
    return temp; 
} 

int main() 
{ 
    X a(2),b(3),c; 

    c=a+b; 

    c.print(); 
    return 0; 
} 

In questo codice, Operator + viene sovraccaricato tramite 2 metodi diversi.Differenza in questi metodi di sovraccarico dell'operatore in C++

La mia domanda è Qual è la differenza tra questi metodi e che dovrebbe essere considerata più pratica da usare?

+1

In C++ 11, 'ritorno {left.i + right.I}' è il metodo # 3 che ... fa anche la stessa cosa (dopo ottimizzazioni banali). – Yakk

risposta

6

non riesco a vedere un caso in cui qualsiasi compilatore genererebbe codice diverso tra queste due versioni. Il secondo è leggermente più dettagliato, ma il compilatore è autorizzato a ottimizzare la copia extra teorica in quel caso e non sono a conoscenza di alcun compilatore che non farebbe elisione.

Detto questo, questo è un micro-ottimizzazione: Scrivere il codice che è più chiara, che poi mi porta al mio ultimo punto. Non scrivere uno di questi operatori, ma scrivere la versione idiomatica in collaborazione con +=:

X& operator+=(const X&right) { i += right.i; return *this; } 
X operator+(X left, const X& right) { return left += right; } 
+0

+1. @ MichaelSmith: Penso che dovresti scegliere questa risposta, ti dà il consiglio giusto su come progettare i tuoi operatori. –

+0

Se hai intenzione di rendere il tuo codice 'constexpr', scrivi' operator + 'e poi scrivi' operator + = 'in termini di ciò. – CTMacUser

3

Non c'è alcuna differenza tra questi due metodi, e dovresti usare quello che meglio comunica il suo intento per te.

Paragrafo 12,8/31 sulla copia elisione specifica:

Quando determinati criteri sono soddisfatti, un'implementazione è consentito di omettere la copia costruzione/spostamento di un oggetto classe , anche se il costruttore preselezionata l'operazione di copia/spostamento e/o il distruttore per l'oggetto hanno effetti collaterali. In tali casi, l'implementazione considera l'origine e la destinazione dell'operazione omessa copia/spostamento come due modi diversi di riferirsi allo stesso oggetto, e la distruzione di quell'oggetto si verifica in un secondo momento in cui i due oggetti sono stati distrutti senza l'ottimizzazione. Questa elisione delle operazioni di copia/spostamento, chiamato copia elisione, è consentito nei seguenti casi (che possono essere combinati per eliminare le copie multiple):

- in una dichiarazione return in una funzione con un tipo di classe di ritorno , quando l'espressione è il nome di un oggetto automatico non volatile (diverso da una funzione o un parametro catch-clause) con lo stesso tipo cv-non qualificato come tipo di ritorno funzione, l'operazione di copia/spostamento può essere omessa costruendo l'oggetto automatico direttamente nel valore di ritorno della funzione

- [...]

Come si può vedere, sia la creazione di una temporanea e una id-espressione denominazione di un oggetto locale con durata memorizzazione automatica qualificano per la copia elision.

Inoltre, il compilatore tratterà locale temp come un rvalue (leggi: come una temporanea, in questo caso) ai fini di ritorno da una funzione. Paragrafo 12.8/32 del C++ 11 specifica standard:

Quando i criteri per l'elisione di un'operazione di copia sono stati raggiunti o sarebbero soddisfatti, salvo per il fatto che l'oggetto di origine è un parametro di funzione, e la l'oggetto da copiare è designato da un lvalue, la risoluzione di sovraccarico a selezionare il costruttore per la copia viene prima eseguita come se l'oggetto sono stati designati da un valore massimo. [...]

A causa di questo, vi consiglierei vivamente rimozione la qualificazione const dal tipo di ritorno:

const X operator + (const X& left, const X&right) 
// ^^^^^ 
// Don't use this! 

In C++ 11, questo inibisce la semantica muoversi, perché non si può passare da un oggetto const, anche se è un valore rvalue - in breve, il costruttore di spostamenti di X, a condizione che ne esiste uno, non verrà selezionato e verrà chiamato il costruttore di copia.

+0

non è il primo ad usare il concetto di "RVO"? correggimi se sbaglio? –

+0

Molto probabilmente, intendeva rendere il metodo const, non l'oggetto di ritorno. –

+1

@ T.E.D .: Non è una funzione membro, quindi non poteva essere 'const' ... –

2

differenza è che Metodo 1 utilizza un concetto chiamato "RETURN valore di ottimizzazione".

Metodo 1:

Quando il compilatore vede che si dispone di alcuna utilità per l'oggetto che sta creando non per restituirlo. Il compilatore ne trae vantaggio e "costruisce l'oggetto direttamente nella posizione in cui questo valore deve essere restituito a". qui è necessaria solo una normale chiamata del costruttore (non è richiesto alcun costruttore di copia) e non vi è alcuna chiamata del distruttore perché non è mai stato effettivamente creato un oggetto locale. questo è più efficiente.


Metodo 2: viene creato

primo oggetto denominato Temp temporanea. quindi copia il costruttore copia la temp in posizione del valore di ritorno di oustide. e quindi il distruttore viene chiamato per temp alla fine dell'ambito.

definitiva Metodo 1 è più efficiente, ma questo è compilatore funzione dipendente.

+4

Ogni compilatore moderno non ritardato emetterà esattamente lo stesso codice per entrambe le versioni. Inoltre, ti stai dimenticando della semantica del movimento. – Griwes

+0

ma nel metodo 2 viene creato l'oggetto temporaneo, inclusa la chiamata del costruttore. –

+0

Forse mi manca qualcosa, ma non vedo perché un ottimizzatore possa trattare queste due routine in modo diverso l'una dall'altra. Entrambi restituiscono un oggetto creato sul posto che non viene utilizzato per alcuno scopo ma restituendo un valore. –

0

La seconda implementazione causerà l'ottimizzazione NRV. Stan Lippman ha detto che l'ottimizzazione NRV aveva bisogno di un costruttore di copie esplicito, ma qui la classe X è abbastanza semplice quindi non credo che NRV abbia bisogno di un costruttore di copie esplicito.