Valore predefinito di un parametro durante il passaggio per riferimento in C ++


117

È possibile assegnare un valore predefinito a un parametro di una funzione mentre stiamo passando il parametro per riferimento. in C ++

Ad esempio, quando provo a dichiarare una funzione come:

virtual const ULONG Write(ULONG &State = 0, bool sequence = true);

Quando lo faccio dà un errore:

errore C2440: "argomento predefinito": impossibile convertire da "const int" a "lungo senza segno e" Un riferimento diverso da "const" non può essere associato a un valore diverso da l


96
Per fortuna, non siamo vincolati dalla guida allo stile di Google.

23
"Non farlo. Le guide di stile di Google (e altri) vietano il passaggio non const per riferimento" Penso che le guide di stile contengano molte parti soggettive. Questo sembra uno di loro.
Johannes Schaub - litb

13
WxWidgets style guide says "don't use templates" and they have good reasons<- screw std :: vector, dico
Johannes Schaub - litb

7
@ jeffamaphone: Google Style Guide dice anche di non utilizzare gli stream. Suggeriresti di evitarli anche tu?
rlbond

10
Un martello è uno strumento orribile per posizionare un cacciavite, ma è abbastanza utilizzabile con i chiodi. Il fatto che si possa fare un uso improprio di una funzione dovrebbe solo avvisarti di possibili insidie ​​ma non proibirne l'uso.
David Rodríguez - dribeas

Risposte:


103

Puoi farlo per un riferimento const, ma non per uno non const. Questo perché C ++ non consente a un riferimento temporaneo (il valore predefinito in questo caso) di essere associato a un riferimento non const.

Un modo per aggirare questo sarebbe utilizzare un'istanza reale come impostazione predefinita:

static int AVAL = 1;

void f( int & x = AVAL ) {
   // stuff
} 

int main() {
     f();       // equivalent to f(AVAL);
}

ma questo è di uso pratico molto limitato.


se lo faccio const, mi permetterà di passare un indirizzo diverso alla funzione? o l'indirizzo dello Stato sarà sempre 0 e quindi privo di significato?

Se stai usando riferimenti, non stai passando indirizzi.

3
boost :: array to the rescue void f (int & x = boost :: array <int, 1> () [0]) {..} :)
Johannes Schaub - litb

1
se non si rivolge a cosa viene effettivamente passato?

2
@Sony Un riferimento. È sbagliato pensarlo come un indirizzo. Se vuoi un indirizzo, usa un puntatore.

33

È già stato detto in uno dei commenti diretti alla tua risposta, ma solo per dichiararlo ufficialmente. Quello che vuoi usare è un sovraccarico:

virtual const ULONG Write(ULONG &State, bool sequence);
inline const ULONG Write()
{
  ULONG state;
  bool sequence = true;
  Write (state, sequence);
}

L'uso dei sovraccarichi di funzione ha anche ulteriori vantaggi. Innanzitutto puoi impostare come predefinito qualsiasi argomento desideri:

class A {}; 
class B {}; 
class C {};

void foo (A const &, B const &, C const &);
void foo (B const &, C const &); // A defaulted
void foo (A const &, C const &); // B defaulted
void foo (C const &); // A & B defaulted etc...

È anche possibile ridefinire gli argomenti predefiniti in funzioni virtuali nella classe derivata, che il sovraccarico evita:

class Base {
public:
  virtual void f1 (int i = 0);  // default '0'

  virtual void f2 (int);
  inline void f2 () {
    f2(0);                      // equivalent to default of '0'
  }
};

class Derived : public Base{
public:
  virtual void f1 (int i = 10);  // default '10'

  using Base::f2;
  virtual void f2 (int);
};

void bar ()
{
  Derived d;
  Base & b (d);
  d.f1 ();   // '10' used
  b.f1 ();   // '0' used

  d.f2 ();   // f1(int) called with '0' 
  b.f2 ();   // f1(int) called with '0
}

C'è solo una situazione in cui è veramente necessario utilizzare un valore predefinito, ed è su un costruttore. Non è possibile chiamare un costruttore da un altro, quindi questa tecnica non funziona in quel caso.


19
Alcune persone pensano che un parametro predefinito sia meno orribile di una moltiplicazione gigante di sovraccarichi.
Mr. Boy

2
Forse alla fine dici: d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'
Pietro

4
Nell'ultima istruzione: da C ++ 11 in poi, è possibile utilizzare costruttori deleganti per chiamare un costruttore dall'altro per evitare la duplicazione del codice.
Fred Schoen

25

C'è ancora il vecchio modo C di fornire argomenti opzionali: un puntatore che può essere NULL quando non è presente:

void write( int *optional = 0 ) {
    if (optional) *optional = 5;
}

Mi piace molto questo metodo, molto breve e semplice. Super pratico se a volte vuoi restituire alcune informazioni aggiuntive, statistiche, ecc. Che di solito non ti servono.
uLoop

11

Questo piccolo modello ti aiuterà:

template<typename T> class ByRef {
public:
    ByRef() {
    }

    ByRef(const T value) : mValue(value) {
    }

    operator T&() const {
        return((T&)mValue);
    }

private:
    T mValue;
};

Allora sarai in grado di:

virtual const ULONG Write(ULONG &State = ByRef<ULONG>(0), bool sequence = true);

Da dove viene l'istanza di ByRefvivere, dal punto di vista della memoria? Non è un oggetto temporaneo che verrebbe distrutto lasciando un ambito (come il costruttore)?
Andrew Cheong

1
@AndrewCheong Il suo intero intento è quello di essere costruito sul posto e distrutto quando la linea è completa. È un mezzo per esporre un riferimento per la durata di una chiamata in modo che un parametro predefinito possa essere fornito anche quando si aspetta un riferimento. Questo codice viene utilizzato in un progetto attivo e funziona come previsto.
Mike Weir

7

No, non è possibile.

Il passaggio per riferimento implica che la funzione potrebbe modificare il valore del parametro. Se il parametro non è fornito dal chiamante e proviene dalla costante predefinita, cosa dovrebbe cambiare la funzione?


3
Il metodo FORTRAN tradizionale sarebbe stato quello di modificare il valore di 0, ma ciò non accade in C ++.
David Thornley,

7

Ci sono due ragioni per passare un argomento per riferimento: (1) per le prestazioni (nel qual caso si desidera passare per riferimento const) e (2) perché è necessaria la possibilità di modificare il valore dell'argomento all'interno della funzione.

Dubito fortemente che passare un lungo senza segno sulle architetture moderne ti stia rallentando troppo. Quindi presumo che tu intenda modificare il valore di Stateall'interno del metodo. Il compilatore si lamenta perché la costante 0non può essere modificata, poiché è un rvalue ("non-lvalue" nel messaggio di errore) e non modificabile ( constnel messaggio di errore).

In poche parole, vuoi un metodo che possa cambiare l'argomento passato, ma per impostazione predefinita vuoi passare un argomento che non può cambiare.

In altre parole, i non constriferimenti devono fare riferimento a variabili effettive. Il valore predefinito nella funzione signature ( 0) non è una variabile reale. Stai riscontrando lo stesso problema di:

struct Foo {
    virtual ULONG Write(ULONG& State, bool sequence = true);
};

Foo f;
ULONG s = 5;
f.Write(s); // perfectly OK, because s is a real variable
f.Write(0); // compiler error, 0 is not a real variable
            // if the value of 0 were changed in the function,
            // I would have no way to refer to the new value

Se non intendi effettivamente cambiare Stateall'interno del metodo, puoi semplicemente cambiarlo in un file const ULONG&. Ma non ne trarrai un grande vantaggio in termini di prestazioni, quindi consiglierei di cambiarlo in un non riferimento ULONG. Ho notato che stai già restituendo un ULONG, e ho il subdolo sospetto che il suo valore sia il valore di Statedopo ogni modifica necessaria. In tal caso, dichiarerei semplicemente il metodo come tale:

// returns value of State
virtual ULONG Write(ULONG State = 0, bool sequence = true);

Ovviamente non sono abbastanza sicuro di cosa stai scrivendo o di dove. Ma questa è un'altra domanda per un'altra volta.


6

Non è possibile utilizzare un valore letterale costante per un parametro predefinito per lo stesso motivo per cui non è possibile utilizzarne uno come parametro per la chiamata di funzione. I valori di riferimento devono avere un indirizzo, i valori dei riferimenti costanti non devono (cioè possono essere valori r o costanti letterali).

int* foo (int& i )
{
   return &i;
}

foo(0); // compiler error.

const int* bar ( const int& i )
{
   return &i;
}

bar(0); // ok.

Assicurati che il tuo valore predefinito abbia un indirizzo e stai bene.

int null_object = 0;

int Write(int &state = null_object, bool sequence = true)
{
   if( &state == &null_object )
   {
      // called with default paramter
      return sequence? 1: rand();
   }
   else
   {
      // called with user parameter
      state += sequence? 1: rand();
      return state;
   }
}

Ho usato questo modello alcune volte in cui avevo un parametro che potrebbe essere una variabile o null. L'approccio normale è che l'utente passi un puntatore, questo è il caso. Passano un puntatore NULL se non vogliono che tu inserisca il valore. Mi piace l'approccio a oggetti nulli. Rende la vita dei chiamanti più facile senza complicare terribilmente il codice chiamato.


IMHO, questo stile è piuttosto "puzzolente". L'unico caso in cui un argomento predefinito è davvero giustificabile è quando viene utilizzato in un costruttore. In ogni altro caso, i sovraccarichi di funzioni forniscono esattamente la stessa semantica senza nessuno degli altri problemi associati ai valori predefiniti.
Richard Corden,

1

Penso di no, e il motivo è che i valori predefiniti vengono valutati in costanti ei valori passati per riferimento devono essere in grado di cambiare, a meno che non si dichiari anche come riferimento costante.


2
I valori predefiniti non vengono "valutati come costanti".

1

Un altro modo potrebbe essere il seguente:

virtual const ULONG Write(ULONG &State, bool sequence = true);

// wrapper
const ULONG Write(bool sequence = true)
{
   ULONG dummy;
   return Write(dummy, sequence);
}

quindi sono possibili le seguenti chiamate:

ULONG State;
object->Write(State, false); // sequence is false, "returns" State
object->Write(State); // assumes sequence = true, "returns" State
object->Write(false); // sequence is false, no "return"
object->Write(); // assumes sequence = true, no "return"

1
void f(const double& v = *(double*) NULL)
{
  if (&v == NULL)
    cout << "default" << endl;
  else
    cout << "other " << v << endl;
}

Funziona. Fondamentalmente, è accedere al valore di riferimento per controllare il riferimento di puntamento NULL (logicamente non esiste un riferimento NULL, solo che ciò che si punta è NULL). Inoltre, se stai usando qualche libreria, che opera su "riferimenti", allora ci saranno generalmente alcune API come "isNull ()" per fare lo stesso per le variabili di riferimento specifiche della libreria. E si suggerisce di utilizzare tali API in questi casi.
parasrish

1

In caso di OO ... Affermare che una determinata classe ha e "Default" significa che questo Default (valore) deve essere dichiarato in modo acondizionato e quindi può essere utilizzato come parametro predefinito es:

class Pagination {
public:
    int currentPage;
    //...
    Pagination() {
        currentPage = 1;
        //...
    }
    // your Default Pagination
    static Pagination& Default() {
        static Pagination pag;
        return pag;
    }
};

Sul tuo metodo ...

 shared_ptr<vector<Auditoria> > 
 findByFilter(Auditoria& audit, Pagination& pagination = Pagination::Default() ) {

Questa soluzione è abbastanza adatta poiché in questo caso "Global default Pagination" è un singolo valore di "riferimento". Avrai anche la possibilità di modificare i valori predefiniti in fase di esecuzione come una configurazione a "livello gobal" es: preferenze di navigazione dell'impaginazione utente ed ecc.


1

È possibile con il qualificatore const per State:

virtual const ULONG Write(const ULONG &State = 0, bool sequence = true);

È inutile e persino ridicolo passare un tempo come un const ref e non ottiene ciò che vuole l'OP.
Jim Balter

0
void revealSelection(const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, bool revealExtent = false);

0

C'è anche un trucco piuttosto sporco per questo:

virtual const ULONG Write(ULONG &&State = 0, bool sequence = true);

In questo caso devi chiamarlo con std::move:

ULONG val = 0;
Write(std::move(val));

È solo una soluzione alternativa divertente, non lo consiglio assolutamente di usare in codice reale!


0

Ho una soluzione alternativa per questo, vedi il seguente esempio sul valore predefinito per int&:

class Helper
{
public:
    int x;
    operator int&() { return x; }
};

// How to use it:
void foo(int &x = Helper())
{

}

Puoi farlo per qualsiasi tipo di dati banale che desideri, come bool, double...


0

Definisci 2 funzioni di sovraccarico.

virtual const ULONG Write(ULONG &State, bool sequence = true);

virtual const ULONG Write(bool sequence = true)
{
    int State = 0;
    return Write(State, sequence);
}

-3

const virtuale ULONG Write (ULONG & State = 0, bool sequence = true);

La risposta è abbastanza semplice e non sono così bravo a spiegare, ma se vuoi passare un valore predefinito a un parametro non const che probabilmente verrà modificato in questa funzione è usarlo in questo modo:

virtual const ULONG Write(ULONG &State = *(ULONG*)0, bool sequence =
> true);

3
Dereferenziare un puntatore NULL è illegale. In alcuni casi potrebbe funzionare, ma è illegale. Leggi di più qui parashift.com/c++-faq-lite/references.html#faq-8.7
Spo1ler
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.