Qualche ragione per non usare lambda globali?


89

Avevamo una funzione che utilizzava una lambda non catturante interna a se stessa, ad esempio:

void foo() {
  auto bar = [](int a, int b){ return a + b; }

  // code using bar(x,y) a bunch of times
}

Ora la funzionalità implementata dalla lambda è diventata necessaria altrove, quindi ho intenzione di sollevare la lambda foo()dall'ambito globale / namespace. Posso lasciarlo come lambda, rendendolo un'opzione copia-incolla o cambiarlo in una funzione corretta:

auto bar = [](int a, int b){ return a + b; } // option 1
int bar(int a, int b){ return a + b; } // option 2

void foo() {
  // code using bar(x,y) a bunch of times
}

Cambiarlo in una funzione adeguata è banale, ma mi chiedo se c'è qualche motivo per non lasciarlo come lambda? C'è qualche motivo per non usare lambda ovunque invece di funzioni globali "regolari"?


Presumo che catturare variabili non intenzionali porterebbe a molti errori se le lambda non fossero usate con attenzione
macroland

Risposte:


61

C'è un motivo molto importante per non usare lambda globali: perché non è normale.

La sintassi delle funzioni regolari di C ++ è in circolazione dai tempi di C. I programmatori hanno saputo per decenni cosa significasse questa sintassi e come funzionano (sebbene, ammettendo che l'intera cosa di decadimento da funzione a puntatore a volte morde anche programmatori esperti). Se un programmatore C ++ di qualsiasi livello di abilità oltre "principiante assoluto" vede una definizione di funzione, sa cosa sta ottenendo.

Una lambda globale è una bestia completamente diversa. Ha un comportamento diverso da una normale funzione. Le lambda sono oggetti, mentre le funzioni no. Hanno un tipo, ma quel tipo è distinto dal tipo della loro funzione. E così via.

Quindi ora hai alzato il tiro nel comunicare con altri programmatori. Un programmatore C ++ deve capire lambdas se capirà cosa sta facendo questa funzione. E sì, questo è il 2019, quindi un programmatore C ++ decente dovrebbe avere un'idea di come appare una lambda. Ma è ancora una barra più alta.

E anche se lo capiscono, la domanda nella mente di quel programmatore sarà ... perché lo scrittore di questo codice l'ha scritto in quel modo? E se non si dispone di una buona risposta per questa domanda (ad esempio, perché si esplicitamente vuole proibire sovraccarico, come in punti Gamme di personalizzazione), quindi si dovrebbe utilizzare il meccanismo comune.

Preferire le soluzioni previste a quelle nuove, se del caso. Usa il metodo meno complicato per farti capire.


9
"dai tempi di C" Molto prima di allora il mio amico
Lightness Races in Orbit il

4
@LightnessRaces: Il mio punto principale era esprimere che la sintassi delle funzioni di C ++ è in circolazione da quando C, quindi molte persone sanno di cosa si tratta attraverso l'ispezione.
Nicol Bolas,

2
Concordo con tutta questa risposta ad eccezione della prima frase. "Non è normale" è un'affermazione vaga e nebulosa. Forse lo intendevi nel senso di "è raro e confuso"?
einpoklum,

1
@einpoklum: C ++ ha molte cose che potrebbero essere considerate "non comuni e confuse" che sono ancora abbastanza "normali" all'interno del loro dominio (vedi metaprogrammazione di template profondi). Penso che "normale" sia perfettamente valido in questo caso; non è una cosa che rientra nelle "norme" dell'esperienza di programmazione C ++, sia storicamente che oggi.
Nicol Bolas,

@NicolBolas: Ma in questo senso, è un ragionamento circolare: OP si chiede se dovremmo adottare una pratica rara. Le pratiche rare non sono "la norma", quasi per definizione. Ma nm.
einpoklum,

53

Posso pensare ad alcuni motivi che vorresti evitare lambda globali come sostituti drop-in per funzioni regolari:

  • le funzioni regolari possono essere sovraccaricate; lambdas non può (ci sono tecniche per simulare questo, tuttavia)
  • Nonostante siano simili a funzioni, anche una lambda non catturante come questa occuperà la memoria (generalmente 1 byte per non catturarla).
    • come sottolineato nei commenti, i compilatori moderni ottimizzeranno questo spazio di archiviazione in base alla regola as-if

"Perché non dovrei usare lambda per sostituire i funzioni stateful (classi)?"

  • le classi hanno semplicemente meno restrizioni rispetto alle lambdas e dovrebbero quindi essere la prima cosa da raggiungere
    • (dati pubblici / privati, sovraccarico, metodi di supporto, ecc.)
  • se la lambda ha uno stato, allora è ancora più difficile ragionare su quando diventa globale.
    • Dovremmo preferire creare un'istanza di una classe nell'ambito più ristretto possibile
  • è già difficile convertire una lambda non catturante in un puntatore a funzione ed è impossibile per una lambda che specifica qualcosa nella sua cattura.
    • le classi ci offrono un modo semplice per creare puntatori a funzioni e sono anche ciò con cui molti programmatori sono più a proprio agio
  • Le lambda con qualsiasi acquisizione non possono essere costruite per impostazione predefinita (in C ++ 20. In precedenza non esisteva in alcun caso un costruttore predefinito)

C'è anche il implicito "questo" puntatore a un lambda (anche uno non di acquisizione) che si traduce in un parametro aggiuntivo quando si chiama lambda vs chiamare la funzione.
1201 Programma Allarme

@ 1201ProgramAlarm: è un buon punto; può essere molto più difficile ottenere un puntatore a funzione per un lambda (sebbene i lambda non catturanti possano decadere in normali puntatori a funzione)
AndyG

3
D'altro canto, se viene passato da qualche parte invece di essere chiamato direttamente, avere una lambda anziché una funzione promuove l'allineamento. Naturalmente, si potrebbe racchiudere il puntatore a funzione in a std::integral_constantper quello ...
Deduplicator

@Deduplicator: " avere una lambda invece di una funzione promuove l'allineamento " Giusto per essere chiari, questo si applica solo se "da qualche parte" è un modello che accetta la funzione che chiama come un tipo chiamabile arbitrariamente, piuttosto che un puntatore a funzione.
Nicol Bolas,

2
@AndyG Stai dimenticando la regola as-if: i programmi che non richiedono la dimensione del lambda (perché ... perché ?!), né creano un puntatore ad esso, non devono (e generalmente non lo fanno) allocare spazio per esso.
Konrad Rudolph,


8

C'è qualche motivo per non usare lambda ovunque invece di funzioni globali "regolari"?

Un problema di un certo livello di complessità richiede una soluzione almeno della stessa complessità. Ma se esiste una soluzione meno complessa per lo stesso problema, non esiste alcuna giustificazione per l'utilizzo di quella più complessa. Perché introdurre complessità che non ti serve?

Tra una lambda e una funzione, una funzione è semplicemente il tipo meno complesso di entità delle due. Non devi giustificare non usare un lambda. Devi giustificare usarne uno. Un'espressione lambda introduce un tipo di chiusura, che è un tipo di classe senza nome con tutte le consuete funzioni dei membri speciali, un operatore di chiamata di funzione e, in questo caso, un operatore di conversione implicita in puntatore di funzione e crea un oggetto di quel tipo. L'inizializzazione della copia di una variabile globale da un'espressione lambda fa semplicemente molto più della semplice definizione di una funzione. Definisce un tipo di classe con sei funzioni dichiarate implicitamente, definisce altre due funzioni operatore e crea un oggetto. Il compilatore deve fare molto di più. Se non hai bisogno di nessuna delle caratteristiche di una lambda, non usare una lambda ...


6

Le lambda sono funzioni anonime .

Se stai usando un lambda chiamato, significa che stai fondamentalmente usando una funzione anonima nominata. Per evitare questo ossimoro, potresti anche usare una funzione.


1
Questo sembra essere un argomento dalla terminologia, vale a dire. denominazione e significato confusi. Può essere facilmente confutato definendo che un lambda di nome non è più anonimo (duh). Il problema qui non è come viene utilizzata la lambda, è che la "funzione anonima" è un termine improprio e non è più precisa quando una lambda è associata a un nome.
Konrad Rudolph,

@KonradRudolph: non è solo la terminologia, ecco perché sono stati creati in primo luogo. Almeno storicamente, le lambda sono funzioni anonime ed è per questo che confondere le loro denominazioni, specialmente quando invece ci si aspetterebbe funzioni.
Eric Duminil,

1
Nah. Le lambda sono chiamate abitualmente (solo di solito localmente), non c'è nulla di confuso al riguardo.
Konrad Rudolph,

1
Non sono d'accordo con le funzioni anonime , è più un functor, ma comunque non vedo il problema di avere un'istanza (nominata) invece di forzare a usarle solo come riferimento al valore. (poiché il tuo punto dovrebbe applicarsi anche a livello locale).
Jarod42,

5

se c'è qualche motivo per non lasciarlo come un lambda? C'è qualche motivo per non usare lambda ovunque invece di funzioni globali "regolari"?

Usavamo le funzioni invece del funzione globale, quindi infrange la coerenza e il Principio del minimo stupore .

Le principali differenze sono:

  • le funzioni possono essere sovraccaricate, mentre i funzioni non possono.
  • le funzioni possono essere trovate con ADL, non con i funzione.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.