2010-02-07 13 views
15

Ho una classe che accumula informazioni su un insieme di oggetti e può agire come un funtore o un iteratore di output. Questo mi permette di fare le cose come:Prevenzione di copie non necessarie di oggetti funtore C++

std::vector<Foo> v; 
Foo const x = std::for_each(v.begin(), v.end(), Joiner<Foo>()); 

e

Foo const x = std::copy(v.begin(), v.end(), Joiner<Foo>()); 

Ora, in teoria, il compilatore dovrebbe essere in grado di utilizzare il copy elision and return-value optimizations in modo che solo un singolo oggetto Joiner deve essere creato. In pratica, tuttavia, la funzione crea una copia su cui operare e quindi la copia nuovamente sul risultato, anche in build completamente ottimizzate.

Se creo il funtore come valore assegnabile, il compilatore crea due copie in più invece di uno:

Joiner<Foo> joiner; 
Foo const x = std::copy(v.begin(), v.end(), joiner); 

Se io goffamente forzare il tipo di modello a un riferimento che passa in un punto di riferimento, ma poi fa una copia di esso comunque e restituisce un riferimento penzoloni alla (ora distrutto) copia temporanea:

x = std::copy<Container::const_iterator, Joiner<Foo>&>(...)); 

posso fare le copie a basso costo utilizzando un riferimento allo stato, piuttosto che lo stato stesso nel funtore in stile di std::inserter, che porta a qualcosa del genere ing come questo:

Foo output; 
std::copy(v.begin(), v.end(), Joiner<Foo>(output)); 

Ma questo rende impossibile usare lo stile "funzionale" di oggetti immutabili, e in generale non è così bello.

C'è qualche modo per incoraggiare il compilatore ad eliminare le copie temporanee, o far passare un riferimento fino in fondo e restituire lo stesso riferimento?

+0

L'utilità del RVO dipende probabilmente dalla definizione di "falegname". Detto questo, sei davvero disposto a rinunciare alla sintassi semplice e pulita 'Foo const x = std :: for_each (v.begin(), v.end(), Joiner ());' qualcosa di molto più brutto? – GManNickG

+0

for_each/copy dovrebbe restituire un Joiner , non un Foo, giusto? Il tuo esempio mi confonde. A meno che tu non stia cercando di implicare Joiner è convertibile in Foo? –

+2

Ho scritto tre diverse risposte per dimostrare che hai torto e alla fine hai capito che non lo sei. Molto utile, grazie per la domanda. +1. –

risposta

14

Ti sei imbattuto in un comportamento spesso lamentato con <algorithm>. Non ci sono restrizioni su cosa possono fare con il funtore, quindi la risposta alla tua domanda è no: non c'è modo di incoraggiare il compilatore ad escludere le copie. Non è (sempre) il compilatore, è l'implementazione della libreria. A loro piace solo aggirare i funtori per valore (si pensi a std :: sort che fa un qsort, passando nel funtore per valore alle chiamate ricorsive, ecc.).

Ti sei anche imbattuto nella soluzione esatta che tutti usano: avere un functor mantenere un riferimento allo stato, quindi tutte le copie si riferiscono allo stesso stato quando questo è desiderato.

ho trovato questo ironico:

Ma questo rende impossibile usare lo stile "funzionale" di oggetti immutabili, e in generale non è così bello.

... poiché questa intera domanda si basa su di voi con un complicato stateful functor, dove creare copie è problematico. Se si usassero oggetti immutabili in stile "funzionale", questo sarebbe un non-problema - le copie extra non sarebbero un problema, vero?

+4

+1 per aver notato l'ironia. – MSN

+0

Sapete se è stato fatto qualche lavoro per risolvere questo problema in C++ 0x. L'ho trovato anche molto fastidioso. In effetti, l'implementazione della mia libreria (al momento) insisteva sul fatto che i functors fossero predefiniti in modo costruttivo, quindi non c'era davvero alcun modo di avere uno stato in essi. – Omnifarious

+0

No, sembra lo stesso in C++ 0x. Penso che sia "per progettazione" (linguaggio non restrittivo che dà più libertà agli implementatori di librerie) –

1

RVO è proprio questo - restituire l'ottimizzazione del valore. La maggior parte dei compilatori, oggi, è attivata per impostazione predefinita. Tuttavia, l'argomento che passa è non restituendo un valore. Non puoi aspettarti che una ottimizzazione si adatti a ovunque.

riferiscono a condizioni per copia elision è definita chiaramente 12.8, comma 15, articolo 3.

quando un oggetto classe temporanea che è non è stato legato ad un riferimento (12.2) sarebbe copiato un oggetto classe con stesso cv-qualificato tipo, l'operazione di copia può essere omesso dal costruire l'oggetto temporaneo direttamente nella destinazione del omessa copia

0.123.

[sottolineatura mia]

Il LHS Foo è const qualificato, la temporanea non è. IMHO, questo preclude la possibilità di copia-elisione.

+0

@Tim Sylvester: aggiornata la mia risposta. – dirkgently

+0

Dichiarare esplicitamente che il tipo di ritorno come valore non const del tipo di functor fa sì che il RVO entri in gioco. Questo certamente lo spiega, anche se sembra strano che tu debba dichiarare una variabile automatica aggiuntiva affinché il compilatore possa ottimizzare via una copia. Nel mio esempio originale, il valore assegnato non è il tipo di funtore. Avrei pensato che il valore di ritorno temporaneo senza nome potrebbe essere l'obiettivo di un RVO poiché il suo tipo implicito sarebbe lo stesso del parametro di valore r. –

+0

@Tim Sylvester: Grazie per aver controllato. Come ho sottolineato, * riguarda * i tipi esatti e non riesco a trovare nulla che dice che ogni conversione è stata eseguita (quale IMO non ha senso in un'operazione di copia - stai copiando valori tra oggetti di tipo _same_). – dirkgently

3

Se si dispone di un compilatore recente (almeno Visual Studio 2008 SP1 o GCC 4.4 credo) è possibile utilizzare std :: Rif/std :: cref

#include <string> 
#include <vector> 
#include <functional> // for std::cref 
#include <algorithm> 
#include <iostream> 

template <typename T> 
class SuperHeavyFunctor 
{ 
    std::vector<char> v500mo; 
    //ban copy 
    SuperHeavyFunctor(const SuperHeavyFunctor&); 
    SuperHeavyFunctor& operator=(const SuperHeavyFunctor&); 
public: 
    SuperHeavyFunctor():v500mo(500*1024*1024){} 
    void operator()(const T& t) const { std::cout << t << std::endl; } 
}; 

int main() 
{ 
    std::vector<std::string> v; v.push_back("Hello"); v.push_back("world"); 
    std::for_each(v.begin(), v.end(), std::cref(SuperHeavyFunctor<std::string>())); 
    return 0; 
} 

Edit: In realtà, l'implementazione del MSVC10 di reference_wrapper non sembra sapere come dedurre il tipo restituito di function object operator(). Ho dovuto derivare SuperHeavyFunctor da std::unary_function<T, void> per farlo funzionare.

+0

Ho provato questo con 'boost :: ref' e non funziona. Con 'std :: tr1 :: ref' (MSVC9) funziona per quegli algoritmi che prevedono un functor come' for_each', ma non per quelli che si aspettano un iteratore come 'copia'. Forse dovrei abbandonare l'uso di "copia" per questo scopo. Grazie! –

+0

Sì, scusate dovrei aggiungere che boost: ref non funziona per questo caso. Questo perché l'operatore boost :: reference_wrapper() non inoltra l'operatore della funzione sottostante() (boost :: reference_wrapper non ha nemmeno un operatore() :) E sull'oggetto funzione che si decompone a un iteratore di output. .. beh, è ​​la prima volta che sento un progetto del genere. Sembra piuttosto strano. –

2

Solo una breve nota, for_each, accumulano, trasformare(2 ° modulo), forniscono alcuna garanzia ordine quando si attraversa il campo fornito.

Questo rende il senso per gli implementatori per fornire versioni multithreading/concurrent di queste funzioni.

Perciò, è ragionevole che l'algoritmo sia in grado di fornire un'istanza equivalente (una nuova copia) del funtore passata.

Diffidate momento functors stateful.

0

Per una soluzione che funzioni con il codice pre-C++ 11, è possibile prendere in considerazione l'uso della funzione boost :: insieme a boost :: ref (come boost::reference_wrapper alone doesn't has an overloaded operator(), diversamente da std :: reference_wrapper che effettivamente funziona). Da questa pagina http://www.boost.org/doc/libs/1_55_0/doc/html/function/tutorial.html#idp95780904, puoi raddoppiare il tuo functor all'interno di un boost: ref quindi un oggetto boost :: function. Ho provato questa soluzione e ha funzionato perfettamente.

Per C++ 11, basta andare con std :: ref e farà il lavoro.

Problemi correlati