2009-11-29 32 views
9
class Foo { 
    protected: 
    QPoint& bar() const; 

    private: 
    QPoint m_bar; 
}; 

QPoint& Foo::bar() const { 
    return m_bar; 
} 

ho ottenuto questo errore:Come restituire correttamente il riferimento al membro della classe?

errore: l'inizializzazione non valido di riferimento di tipo 'QPoint &' da un'espressione di tipo 'const QPoint'

Tuttavia funziona se cambio a questo:

QPoint& Foo::bar() const { 
    return (QPoint&) m_bar; 
} 

1) Non capisco perché il compilatore dice che il mio QPoint è const.

2) È giusto lasciare il cast lì?

risposta

21

In una funzione membro non-const della classe Foo puntatore this è del tipo Foo* const - cioè, il puntatore è const, ma non l'istanza cui punta. In una funzione membro const, tuttavia, il puntatore this è del tipo const Foo* const. Ciò significa che anche l'oggetto a cui punta è costante.

Pertanto, nel tuo esempio, quando si utilizza this->m_bar (di cui m_bar è solo la forma abbreviata), poi m_bar è un membro di un oggetto costante - che è il motivo per cui non è possibile tornare come riferimento non-const.

Questo rende effettivamente senso da un POV di progettazione: Se questo Foo oggetto è un oggetto costante, e si è permesso di richiamare Foo::bar per gli oggetti costanti, quindi, se questo sarebbe ritornato un riferimento non const ad alcuni interni cui è possibile giocherellare con, saresti in grado di cambiare lo stato di un oggetto costante.

Ora devi guardare il tuo disegno e chiediti come sei arrivato a questo punto e qual è la tua intenzione attuale. Se il membro m_bar non fa realmente parte dello stato dell'oggetto (ad esempio, è solo lì per scopi di debug), allora potresti prendere in considerazione l'idea di renderlo mutable. Se fa parte dello stato dell'oggetto, allora devi chiederti perché vuoi restituire un riferimento non-const ad alcuni dati interni di un oggetto costante. O rendere la funzione membro non-const, o restituire un riferimento const o sovraccaricare la funzione membro:

class Foo { 
public: 
    const QPoint& bar() const {return m_bar;} 
     QPoint& bar()  {return m_bar;} 
    // ... 
}; 
+2

Grazie. Pensavo che il qualificatore di funzione const fosse pensato solo per garantire che la funzione stessa non modificasse l'oggetto. Voglio che m_bar sia modificabile, quindi ho rimosso il qualificatore. – Dimitris

+0

In realtà impedisce alla funzione di modificare l'oggetto, ma lo fa solo consegnando un oggetto const alla funzione. Tuttavia, la funzione per la quale non è possibile fornire un riferimento non const è più di un effetto collaterale accidentale: non si dovrebbe essere in grado di ottenere riferimenti non const a un oggetto costante in alcun modo. – sbi

3

Il const sulla funzione indica al compilatore che la funzione non modifica alcuna variabile membro. Tuttavia, dal momento che si restituisce un riferimento alla variabile membro, questo non può più essere garantito e quindi ha l'errore di compilazione.

Il compilatore non si aspetta che la variabile membro sia const, ma il ritorno dalla funzione deve essere const. Rimuovi il const dalla funzione def.

4

Quello che stai cercando di fare è un no-no. Non si desidera restituire un QPoint & dalla funzione della barra in quanto ciò interrompe l'incapsulamento perché un chiamante può ora modificare m_bar fuori da sotto QPoint. (fortunatamente hai la funzione dichiarata come const o non otterrai un errore qui e staresti ancora rompendo l'incapsulamento).

Che cosa si vuole in questo caso è

const QPoint &Foo::bar() const 
{ 
    return m_bar; 
} 

Modifica sulla base Commento dell'utente:

IMHO, questo sarebbe meglio risolto con l'aggiunta di un setX a Foo invece di chiamare un non-const funzione da un riferimento non-const restituito da una funzione accessor che dovrebbe essere effettivamente const. Sovraccaricare la funzione accessor per rimuovere il const mette semplicemente un cerotto sul vero problema e nasconde semplicemente il fatto che l'incapsulamento è compromesso. L'aggiunta di setX a Foo risolve il problema di incapsulamento e inoltre ricollega l'interfaccia che si sta creando creando un riferimento non costante a dati privati.

Ad esempio, se si mette foo.bar().setX(100); in molte parti della vostra applicazione e poi cambia il tipo di QPoint a uno che non implementa setX o semplicemente rinominare la funzione setX dire setPointX avete problemi b/c ora avete per aggirare un rinominatore/refactatore di tutte queste chiamate. La creazione di un setX su Foo rende il codice più semplice da chiamare foo.setX(100) rispetto a foo.bar().setX(100), è const corretto e incapsula i tuoi dati. Se hai cambiato QPoint per avere solo 2 coordinate xey in Foo, nulla al di fuori della tua classe dovrebbe cambiare b/c hai una buona incapsulamento.

+0

Foo :: bar() è un accessorio. Voglio essere in grado di modificare QPoint come foo.bar(). SetX (100); Pensavo che il qualificatore della funzione const avesse lo scopo di garantire che la funzione stessa non modificherebbe l'oggetto. Grazie lo stesso! – Dimitris

+0

Ho una risposta aggiornata in base al tuo feedback. –

3

usare sia

const QPoint& Foo::bar() const { 
    return m_bar; 
} 

o

QPoint& Foo::bar() { 
    return m_bar; 
} 

Credo che si potrebbe anche dichiarare m_bar come:

mutable QPoint m_bar; 

EDIT: difesa mutevole

@tstenner:
mutable ha il suo posto. Non è "malvagio" definitivamente non più del vuoto * o del casting. Assumere qualcosa come il seguente:

class foo { 
    SomeType bar; 

public: 
    foo() : bar() { } 

    const SomeType& bar() const { return a; } 
}; 

In questo caso, il bar è sempre costruito, anche se bar() non viene mai chiamato. Questo potrebbe andar bene, ma se SomeType ha un costoso costruttore, può essere preferibile consentire l'istanziazione lenta della barra.

considerare:

class foo2 { 
    mutable SomeType* bar; 
    mutable bool cached; 

public: 
    foo2() : bar(0), cached(false) { } 

    const SomeType& bar() const { 
     if(! cached) { 
      cached = true; 
      bar = new SomeType(); 
     } 
     return *bar; 
    } 
}; 

Questo permette foo2 di imitare una classe un'istanza non pigri, pur essendo pigramente istanziato. Certo, la mutabilità di foo2 ha implicazioni per la sicurezza dei thread, ma questi possono essere superati con il blocco se necessario.

+2

Non si vuole usare il mutabile, perché è malvagio. – tstenner

+1

Il mutabile dovrebbe essere usato con parsimonia, ma ha usi che non sono malvagi. Ad esempio, se si dispone di una funzione di accesso/membro const che deve leggere un oggetto a cui si accede da più thread ed è sincronizzata tramite un mutex di classe, sarà necessario rendere mutex mutabile affinché le funzioni const possano leggere l'oggetto in modo sicuro. –

2

Se è consentito restituire un riferimento alla variabile, è possibile modificare un'istanza della classe . Considerare tale codice:

void baz(const Foo &foo) 
{ 
    QPoint &ref = foo.bar(); //if it was allowed... 
    ref = 5; // ...you could have modified a const object foo! 
} 

Pertanto il compilatore impedisce di farlo.

È necessario dichiarare il metodo restituendo const QPoint& o rivedere la propria comprensione di ciò che è veramente const.

Qualcuno potrebbe consigliare di utilizzare mutable. Non farlo. La parola chiave mutable è consentire l'implementazione della classe per modificare le variabili dei membri interni degli oggetti const (ad esempio, per scopi di memoizzazione), piuttosto che esporre la variabile non const (!) Al codice esterno.

Problemi correlati