Sintassi del passaggio lambda

0

In questo momento, sto lavorando al refactoring di un programma che chiama le sue parti eseguendo il polling su una struttura più guidata dagli eventi.

Ho creato le classi di pianificazione e attività con lo sced per diventare una base classe del ciclo principale corrente. Le attività saranno create per ciascuno metro in modo che possano essere richiamati da quello invece del polling.

Ciascuna delle chiamate principali degli eventi è un tipo di contatore che raccoglie informazioni e mostralo. Quando il programma sta arrivando, tutti i metri abilitati ottenere 'costruito' da un main-sub. In quel sottotitolo, voglio memorizza anche il puntatore "questo" associato al contatore come il nome comune per la "routine di azione.

void MeterMaker::Meter_n_Task (Meter * newmeter,) {
  push(newmeter);      // handle non-timed draw events
  Task t  = new Task(now() + 0.5L);
  t.period={0,1U};
  t.work_meter = newmeter;      
  t.work  = [&newmeter](){newmeter.checkevent();};<<--attempt at lambda
  t.flags = T_Repeat;
  t.enable_task();
  _xos->sched_insert(t);
}

Una chiamata di esempio:

Meter_n_Task(new CPUMeter(_xos, "CPU "));

ha reso lo schedulatore una classe base della routine principale (che gestisce il ciclo), e ho provato varianti serveral per ottenere la classe di attività essere una base della classe metro, ma continuare a correre nei posti di blocco. È molto simile a "whack-a-mole" - sterlina in qualcosa per aggiustare qualcosa posto, e poi un nuovo file si apre altrove.

Parte del problema è il file sched.h che sta provando per contenere l'attività Q, include il file di intestazione dell'attività. L'obiettivo file Vuole fare riferimento alla classe più "base", Meter .

La classe del misuratore tira nella classe principale del genitore mentre passa una copia del genitore ai bambini in modo che possano accedere al sorteggio routine nel genitore.

Due riferimenti nel file di attività sono per il puntatore "this" di il contatore e il sottomenu di aggiornamento del contatore (da chiamare tramite questo).

void *this_data= NULL;
void (*this_func)() = NULL;

Nota: non volevo davvero memorizzarli nella classe, come volevo per usare un lamdba nella routine di lavoro del metronomo sopra indicata memorizzare una routine + contesto da utilizzare per chiamare la routine di azione dello strumento.

Impossibile capire la sintassi.

Ma sto correndo in altri problemi di sintassi che cercano di memorizzare il file puntatori ... come

  g++: COMPILE lsched.cc
In file included from meter.h:13:0,
                 from ltask.h:17,
                 from lsched.h:13,
                 from lsched.cc:13:
xosview.h:30:47: error: expected class-name before ‘{’ token
 class XOSView : public XWin, public Scheduler {

Come sopra dove chiede una classe, dove è il nome di classe "Scheduler". !?!? Eh? Questo è un nome di classe.

Continuo ad andare in cerchio con cose che non hanno senso ... Idealmente, farei funzionare la lamba nella routine Meter_n_Task in cima. Volevo solo memorizzare 1 puntatore nel 'Task' classe che era un puntatore al mio lambda che avrebbe già catturato il "questo" valore ... ma non è riuscito a far funzionare la sintassi affatto quando ho provato ad avviarlo in una var nella classe 'Task'.

Questo progetto, FWIW, è il mio progetto di dentizione sul nuovo C ++ ... (ovviamente è semplice! ..; -)) ... Ho fatto un bel po 'di progressi in altri aree nel codice, ma questa sintassi lambda mi ha bloccato ... è a volte come quello che apprezzo la facilità di questo tipo di operazione in perl. Sigh.

Non sono sicuro che il modo migliore per chiedere aiuto qui, in quanto questo non è un semplice domanda. Ma ho pensato di provare! ...; -)

Peccato che non possa allegare file a questo Q.

    
posta Astara 08.06.2014 - 04:59
fonte

2 risposte

5

Il tuo problema principale sembra essere il fatto che il tuo design rende le tue classi troppo informate l'una sull'altra, il che ti darà grattacapi con dipendenze cicliche e problemi di proprietà inestricabili, specialmente dal fatto che apparentemente stai usando molto i puntatori grezzi.

La tua Task astrazione non dovrebbe sapere nulla su Meter , o se lo fa, dovrebbe conoscere una classe base astratta per i tuoi contatori, e fare affidamento esclusivamente su questo. E Meter dovrebbe essere completamente ignaro del sistema Task . Ciò ti consentirà di avere un'astrazione di task e scheduler che sono completamente generiche o dipendono solo da un'interfaccia ben definita.

Si afferma nei commenti che andare con una classe base astratta e metodi virtuali sarebbe troppo pesante, quindi un'astrazione "generica" dovrebbe essere la strada da percorrere. Gli strumenti per crearlo in C ++ potrebbero essere modelli o, in questo caso, utilizzare un std::function per nascondere il tipo effettivo (e la "natura") dell'attività da eseguire.

Ecco una configurazione minima delle attività, dovrai aggiungere lo stato delle tue attività (abilitato / disabilitato, prossima ora di inizio, ecc.) e adattare lo scheduler per usarlo: (vedi Come posso memorizzare un'espressione lambda come campo di una classe in C ++ 11? )

#include <functional>

struct Task
{
    Task(std::function<bool()> f): func(f) {}
    bool execute()
    {
        return func();
    }
private:
    std::function<bool()> func;
};

Puoi usare questa utility per programmare le funzioni ordinarie, o lambdas - std::function si occupa di nascondere il tipo effettivo di callable che gestisce per te.

Esempio di utilizzo:

#include <iostream>
#include <vector>

#include "task.h"

bool foo()
{
    std::cout << "Fooing" << std::endl;
    return true;
}

struct Meter
{
    Meter(std::string n): name(n) {}
    bool process_events()
    {
        std::cout << "Processing " << name << std::endl;
        return true;
    }
private:
    std::string name;
};

int main()
{
    Meter m { "CPU" };
    std::vector<Task> tasks;
    tasks.emplace_back(foo);
    tasks.emplace_back([&m](){return m.process_events();});

    for (auto& task: tasks)
        task.execute();
}

Ora sembra sbagliato:

class XOSView : public XWin, public Scheduler { ... };

Una finestra non è un programmatore, uno schedulatore non è una finestra e neanche un gioco è uno di questi. Questo tipo di costrutto ti darà più grattacapi andando oltre quello che già hai. Usa composizione . Il tuo gioco dovrebbe avere un'interfaccia utente e avere uno schedulatore e un mucchio di altre cose. Non dovrebbe essere un'interfaccia utente e un programmatore allo stesso tempo.

_xos->sched_insert(t);

Trasforma quello per usare un oggetto schedulatore utente:

_xos->sched().insert(t);

Quindi scopri se i tuoi contatori devono conoscere il controller di gioco o l'interfaccia utente. Se hanno bisogno di entrambi (ciò che capisco dal tuo commento), passa un riferimento ad entrambi durante la costruzione fino al momento in cui puoi creare un'interfaccia migliore tra quelle.

L'ereditarietà multipla è molto facile da sbagliare, ancor più della semplice ereditarietà (già abbastanza difficile). Evitalo a meno che tu non conosca meglio.

Consiglio casuale:

  • Assicurati di comprendere la differenza tra una dichiarazione e una definizione di classe e cosa puoi fare con un tipo incompleto (su ciò che è stato dichiarato ma non definito). Vedi ad esempio Non identifica un errore di tipo in C ++ , e per maggiori informazioni Quando posso utilizzare una dichiarazione anticipata? .
  • Non cercare di ignorare un errore di tipo con void* . Comprendi il tuo problema e risolvilo. I tipi sono fondamentali per C ++ e void* è il più vicino possibile a un "non-tipo". Il compilatore non può aiutarti con esso, non può avvisarti di errori di tipo quando lo usi.
  • Sii più attento con la tua sintassi. Se t è un puntatore t.foo() è sbagliato. Questo non è Java. Le classi e le definizioni di struct devono terminare con un ; ( struct foo {}; ). Dimenticando che il punto e virgola ti porterà a errori del compilatore molto bizzarri.
  • Non utilizzare new , non è Java. Utilizza std::shared_ptr e / o std::unique_ptr con i relativi helper std::make_shared / std::make_unique . Ciò consentirà di risolvere molti problemi di gestione della memoria e trucchi di sicurezza eccezionali.
  • Altre letture su lambdas: Che cosa è un'espressione lambda in C ++ 11 ? , Lambda Expressions
risposta data 08.06.2014 - 21:35
fonte
0

@Mat, sopra, ha portato molti buoni punti, e alla fine ho usato una "funzione" da "std :: functional" per definire l'elemento di dati "work" in "Task". Tuttavia, c'era una discreta quantità di dettagli di distrazione su "cosa era cosa" (gerarchia di classe / derivazione) e dettagli di codifica (uso di "nuovi", "void *" e problemi di sintassi) dettagli da usare. Così, mentre ho invalutato la tua risposta, sto includendo la soluzione che ho implementato in quel momento, che ha funzionato, come una registrazione dettagliata di come ho risolto il problema del passaggio dei dati a una funzione lambda [tipo] (usando std :: funzione funzionale). Ammetto che questo è diventato un soluzione rotonda, ma sembrava adattarsi meglio al problema.

Come prefazione, dovrei dire che trovo che i "typedef" (o i loro equivalenti "che usano") e le funzioni di aiuto sono quasi "obbligatori" nell'aiutare a capire cosa sta succedendo.

Nel "codice del driver", ho semplificato la sua chiamata alla configurazione di diversi "misuratori" a qualcosa che assomiglia a:

#ifdef _XXX_METER_
  if (meterclass_on("xxx_meter")) Meter_n_Task(new XXX_Meter());
#endif

I dati che dovevo passare erano una funzione che indicava il lavoro che doveva essere eseguito: una funzione di raccolta dei dati doveva essere chiamata indipendentemente dalle funzioni di visualizzazione della classe di driver di primo livello. In ogni classe in possesso di un tipo di 'metro', viene chiamata una funzione 'checkevent' (evento che consiste nel "raccogliere dati correnti"). Quindi nella classe per tutti i contatori di tipo "XXX", checkevent è il lavoro che deve essere eseguito periodicamente dall'Utilità di pianificazione.

Il costruttore della classe, ad esempio per "traffico di rete", "NetMeter", viene alimentato a classe "istanza" che rappresenta tutte le interfacce se si desidera un sommario o alimentato istanze specifiche dell'interfaccia per statistiche più specifiche. In entrambi i casi, sono basati su "Meter", che deriva da una classe "Task", che definisce una funzione di "lavoro" generale, come in:

function<void (void) > work{};

Nel costruttore per un 'metro', al lavoro è stato assegnato il valore dei dati routine di raccolta, "checkevent" come ultima (e unica) "istruzione" nel costruttore. Gli altri valori di definizione del contatore sono stati gestiti nella definizione del contatore, ala:

NetMeter::NetMeter (const Instance_Spec &instance ) : 
    FieldMeterGraph(instance),
    DataSrc( IOSrc(proc_netdev),    
      Data_ops( {   new SyntaxFn(net_syntax_filter),
                  new PositionMap(select_fields),
                  new Dev_Matcher((const char *)Xresname),
                  new ByDelta(1000U) }), 256*K) {

  work  =   [this](){checkevent();};
}

BTW, per capire perché un multimetro era un compito: in questo quadro, un multimetro, tra le altre cose, rappresentava un'attività periodica che doveva essere eseguita (raccolta dati + elaborazione con data e ora). Allo stesso modo, il driver di livello superiore (che era un'interfaccia finestra), era anche l'Utilità di pianificazione. Spero che questo spieghi perché il codice del driver di primo livello abbia:

class XOSView : public XWin, public Scheduler { ... };

Infine, la funzione Meter_n_Task è dove il contatore è stato aggiunto all'elenco "todo" del conducente, la finestra e la chiamata "funky" per aggiungere lo strumento all'utilità di pianificazione:

void MeterMaker::Meter_n_Task (Meter * newmeter) {
  push(newmeter);      // handle non-timed draw events
  _xos->insert_new(newmeter);
  ( (Task *) newmeter)->taskname( ( (Meter *) newmeter)->name() );
}

Ricorda che il "lavoro" era già "incorporato" nei dati del "misuratore" assegnato a "lavoro" nel costruttore della classe derivata. Il lavoro del misuratore è stato quindi incluso nella classe meter e il valore è stato inserito nella coda di esecuzione dello scheduler in base al costruttore della classe "Task". Fondamentalmente, tutto il lavoro di installazione è fatto nei costruttori, quindi sono sicuro che tutto è pronto prima che qualsiasi cosa faccia riferimento ai contatori.

Lo Scheduler viene avviato da 'main' dopo aver eseguito la propria attività di inizializzazione.

Ad ogni modo, ho iniziato a fare più refactoring dopo aver terminato quanto sopra, che è stato interrotto alcuni anni fa (ahi) ... Dovrò vedere come va.

    
risposta data 14.04.2017 - 03:13
fonte

Leggi altre domande sui tag