2010-03-09 17 views
5

Sto sviluppando una libreria in C++ in cui gli utenti/programmatori estenderanno una classe BaseClass che ha un metodo initArray. Questo metodo dovrebbe essere implementato dall'utente/programmatore e normalmente dovrebbe inizializzare tutti gli elementi dell'array m_arr.Rileva modifica della variabile in fase di esecuzione in C/C++

Ecco una snipplet, modificato per questo esempio:

class BaseClass { 
    public: 

    BaseClass(int n) { 
     m_arr = new double[n]; 
     size = n; 
    }; 
    virtual ~BaseClass(); 

    int size; 
    double* m_arr; 

    virtual int initArray(); 
}; 

A volte, l'utente/programmatore implementa una initArray che non inizializza alcuni elementi di m_arr. Quello che vorrei è creare una funzione nella mia libreria che controlli se initArray ha inizializzato tutti gli elementi di m_arr. Questa funzione deve essere richiamata da una routine di controllo di integrità al runtime .

La mia domanda: è possibile rilevare le modifiche su questo array? Posso solo pensare di inizializzare l'array con alcuni valori non validi (come NaN o Inf), chiamare initArray e controllare che tutti i valori siano cambiati.

Grazie per le vostre idee,

David


Modifica

Ecco un esempio del codice client che sto cercando di rilevare:

// .h: 
class MyExample : public BaseClass { 
    public: 
    MyExample(); 
    virtual ~MyExample(); 
    virtual int initArray(); 
}; 

// .cpp: 
MyExample::MyExample() : BaseClass(3) { } 
MyExample::~MyExample() { } 
int MyExample::initArray() { 
    m_arr[0] = 10; 
    //m_arr[1] = 11; // let's say someone forgot this line 
    m_arr[2] = 12; 
    return 0; 
} 

Quindi, dimenticando lo m_arr[1] questo elemento non è inizializzato e potrebbe causare problemi nei calcoli futuri. Questo è quello che vorrei controllare.

+6

Certo, rendere privato m_arr e fornire accessors. –

risposta

3

Non vedo un modo semplice per farlo in C++. Quello che intendi implementare sono i filtri in Ruby on Rails dove prima di accedere a qualsiasi metodo, i filtri sono invocati.

In alternativa è possibile avvolgere la matrice in una struttura e all'interno di questa struttura sovraccaricare l'operatore [] per l'assegnazione e l'accesso.

Ora:

1) All'interno della sovraccarico [] operatore per l'assegnazione, mantenere un contatore e incrementare il contatore per ogni inizializzazione.

2) All'interno dell'operatore di accesso [] sovraccarico, controllare il contatore prima di accedere ai contenuti dell'array se è uguale al numero totale di elementi. In caso contrario, lanciare un errore.

Spero che questo aiuti.

+0

Mi piace questa risposta perché rimane con la struttura proposta dall'esempio. – YuppieNetworking

+0

m_arr [0] = 10; m_arr [0] = 11; // Oops, bug di copia-incolla! m_arr [2] = 12; –

+0

@ Marco grazie per averlo indicato. È possibile aggiungere una condizione prima di incrementare il contatore per verificare se l'elemento corrente non è già inizializzato – mukeshkumar

5

Perché non utilizzare un vettore std ::? L'utente finale aggiungerà quindi a esso utilizzando push_back e sarà possibile controllare la dimensione per vedere quanti elementi sono stati aggiunti.

+0

Perché sto mantenendo questo codice e utilizza molto la struttura dell'array per accoppiare la libreria con alcune librerie scritte in C. – YuppieNetworking

+0

@YuppieNetworking È possibile utilizzare i vettori con il codice C. –

+0

@Neil Butterworth Nota che std :: vector viene riallocato su alcune delle modifiche alle dimensioni e anche le posizioni di memoria degli elementi possono essere modificate. Questo è a volte critico. – doc

1

L'idea di utilizzare inf o NaN funzionerebbe. Qualsiasi inizializzatore predeterminato funzionerebbe abbastanza bene.

Un'altra idea è creare una funzione membro di accesso per modificare elementi. Nella finestra di accesso si imposta il flag modificato, in InitArray() si cancella il flag modificato.

+1

Idea con NaNs funzionerà fino a quando la sottoclasse non sta inizializzando la matrice con NaNs da sola. Sfortunatamente questo potrebbe far parte della funzionalità della sottoclasse. Immaginiamo che la sottoclasse prenda due parametri 'a, b' e il valore iniziale sia' a/b'. Il NaN risultante (ad es. In caso di a = 0 eb = 0) è una parte della sua specifica. Oppure, dall'altra parte, l'inizializzazione può essere così complessa da rendere involontariamente in grado di restituire NaN a causa di errori di arrotondamento. Possibili errori che possono verificarsi con tale approccio possono essere molto difficili da tracciare. – doc

+0

@doc: ottimo punto – YuppieNetworking

0

Questo dipende da cosa si sta tentando di fare: è effettivamente necessario creare l'array nella classe base oppure è possibile utilizzare il suggerimento di Neil di push_back()?

In ogni caso, è consigliabile utilizzare un std::vector<> (perché la gestione della memoria?) Di boost::optional<double>. (Sai dello boost libraries?)

Inoltre, vuoi veramente dividere la creazione dell'oggetto in due fasi? Qualsiasi codice client che crea un discendente BaseClass deve chiamare initArray(). Sei preoccupato per l'efficienza?

+0

Questo design è dovuto al fatto che BaseClass è in realtà un oggetto con stati (come in una simulazione). initArray inizializza lo stato dell'oggetto e potrebbe essere inizializzato più volte man mano che gli stati cambiano. – YuppieNetworking

+0

'boost :: optional' è ottimo ma non può essere esposto in modo sicuro a C-code. –

2

Ecco la mia proposta:

Aggiungi un altro metodo virtuale protetto alla classe base, che chiama "InitArrayImpl" e ordinare il creatore della sottoclasse per riempire la matrice in esso.

Il metodo initArray deve essere pubblico non virtul, in questo metodo, effettuare una chiamata a InitArrayImpl, prima della chiamata, inizializzare l'array con valori non validi, dopo la chiamata, verificare se tutti i valori sono stati modificati.

Il client della classe deve essere esposto solo al metodo initArray.

+0

void Base :: initArray() {initArrayWithInvalidValue(); initArrayImpl(); checkArray(); } dove initArrayImpl() è una funzione virtuale. – Corwin

+0

Sì, i miei pensieri iniziali erano in questa direzione. Ecco perché ti ho chiesto ragazzi, dal momento che probabilmente potrebbe essere fatto in un altro modo. – YuppieNetworking

+0

Questo è il modo idiomatico. Infatti se ascolti il ​​consiglio di Herb Sutter (ed è come se sapesse qualcosa giusto?) Le interfacce non dovrebbero mai esporre metodi virtuali. metodi non virtuali che invocano metodi virtuali privati ​​consentono di verificare le condizioni pre- e post-post nella classe base. –

3

Se stai cercando di creare un'interfaccia "infallibile", piuttosto che pensare a initArray() come una routine con effetti collaterali ... perché non stilizzarlo come qualcosa di più funzionale? initArray() potrebbe avere la responsabilità di allocare e costruire un vettore e ritrasferire un riferimento a quell'oggetto completamente costruito. Ciò consentirebbe anche di effettuare m_arr privato:

class BaseClass { 
private: 
    size_t size; 
    auto_ptr< vector<double> > m_arr; 

public: 
    BaseClass(size_t n) { 
     size = n; 
    }; 
    virtual ~BaseClass(); 

protected: 
    virtual auto_ptr< vector<double> > initArray() const; 
}; 

(Nota: Dopo aver chiamato initArray è possibile verificare che il vettore della classe derivata passa di nuovo è la dimensione giusta O se non avete bisogno di far rispettare. la dimensione in avanti, si può solo annullare quel parametro dal costruttore e accettare qualsiasi lunghezza initArray restituisce come dimensione desiderata.)

+0

Sono d'accordo con il tuo approccio funzionale. Penso che andrò da questa parte poiché mi piace la routine senza effetti collaterali. Tuttavia, come ho detto a Neil, ho un sacco di utenti/programmatori che già codificano con initArray e i suoi effetti collaterali, quindi speravo di migliorare alcuni dettagli su questa libreria con un basso impatto sulle loro abitudini. – YuppieNetworking

0

Penso che tu stia fischiando nel vento su questo. Non è possibile dettare che tutti i valori siano correttamente compilati. Nel migliore dei casi tutto ciò che si può fare è dettare che l'utente inserisce qualche valore negli elementi dell'array. Se metti i NaN nell'array che controlli quindi, tutti gli utenti faranno un'inizializzazione con zero, -1 o MAX_DOUBLE. Quindi potresti anche dare loro un ctor per farlo e averne fatto.

La mia classe derivata potrebbe utilizzare la classe base per riservare 1000 elementi ma potrebbe contenere il proprio conteggio di quanti elementi ha effettivamente utilizzato.

1

Se l'efficienza non è uno dei vostri principali obiettivi, si può

  • Blocca l'accesso diretto alla m_arr rendendolo privato
  • aggiungere array di Caccio della stessa dimensione come m_arr m_field_initialized = new bool[n] e inizializzare con valori falsi.
  • Crea istanza pubblica (o protetto) di classe di accesso che vi avvolgerà m_arr e m_field_initialized
  • Accessor dovrebbe avere setVal(int i, double val) metodo che definirà m_arr[i] e additionaly m_field_initialized[i] bandiera true;
  • Nel test di verifica del test di inizializzazione se tutti i campi di m_field_initialized sono stati impostati su true.

È possibile migliorare questo fornendo un metodo per un accesso diretto più rapido, quando sono state soddisfatte le condizioni di inizializzazione. Restituisce un puntatore nullo prima del controllo di inizializzazione e, una volta completata l'inizializzazione, restituisce il puntatore dell'array.

double * directAccess() { 
    if (m_arr_initialized) return m_arr; 
    else return 0; 
} 

m_arr_initialized è fissato per il metodo di controllo di inizializzazione.


Se non è richiesta per la matrice da allocare nella classe base è possibile impostare m_arr a zero, lasciare allocazione per sottoclassi e solo controllare se m_arr puntatore è stato impostato diverso da zero. È possibile impostare un campo valido aggiuntivo che denoti la dimensione allocata.In alternativa, puoi bloccare l'accesso a m_arr e fornire il metodo nella classe base per l'allocazione allocate(std::size_t size) o l'allocazione con il valore iniziale allocate(std::size_t size, double initVal) oppure imporre la funzione di inizializzazione da passare allocate(std::size_t size, double (*callback)(std::size_t element)), che verrà richiamata su ciascun elemento. Ci sono molte possibilità.


edit: dopo la modifica suggerisco un puntatore (o di riferimento) a uno oggetto inizializzazione o richiamata ad una funzione nel costruttore BaseClass. Ciò imporrà le sottoclassi per fornire il codice di inizializzazione. Si consideri il seguente:

class InitializerInterface 
{ 
    public: 
    virtual double get(int element) const = 0; 
} 

Nella tua classe base costruttore

BaseClass(int n, const InitializerInterface & initializer) { 
    m_arr = new double[n]; 
    size = n; 
    for (int i = 0; i < n; i++) 
     m_arr[i] = initializer.get(i); 
}; 

Ora, qualsiasi sottoclasse deve passare un po 'di inizializzazione al costruttore. Ovviamente è possibile sostituire il metodo get() con un functor o aggiungere anche il supporto per la funzione callback. Dipende dalle tue preferenze.

// ultima modifica per renderlo const-corretto

1

Questo appare meno come C++ e più simile a C con un paio di caratteristiche utili come costruttori per me. La tua classe non può prendere una decisione se si tratta di una classe C-struct o C++ e penso che sia parte del problema che stai avendo con l'inizializzazione qui.

Cosa fare in questo modo in un modo diverso, rendendo privato l'array e fornendo un'interfaccia di inizializzazione non virtuale che accetta un tipo di functor simile a std :: generate. Quindi chiama il funtore una volta per ciascun elemento. In questo modo sai se hanno chiamato il tuo inizializzatore e sai che tutti gli elementi sono inizializzati in modo sicuro. Non solo, vengono quindi protetti dalle classi dei bambini cambiandoli quando lo desiderano.

Se per un motivo o l'altro è necessario mantenere l'approccio corrente, l'utilizzo di NaN o inf potrebbe funzionare se è possibile garantire che tali valori non catturino un'eccezione su qualsiasi hardware per cui si prevede di rilasciare la libreria. Più sicuro, basta scegliere un valore neutro come 0 o uno e se il client non riesce a inizializzare, è sufficiente chiarire che si stanno sparando ai loro piedi.

In altre parole, avere tali dati essere pubblici E far rispettare che viene inizializzato sanamente sono due (quasi) obiettivi che si escludono a vicenda.

+0

Sembra un paio di funzionalità C + perché il codice è pesantemente tagliato. Sono d'accordo con il tuo punto NaN. – YuppieNetworking

0

È anche possibile impostare un punto di interruzione dei dati tramite registri di debug. Questo link ha alcune informazioni sull'argomento.

+0

Grazie, ma sto cercando una soluzione runtime, senza debugger. – YuppieNetworking

Problemi correlati