Valor predeterminado de un parámetro mientras se pasa por referencia en C ++


116

¿Es posible dar un valor predeterminado a un parámetro de una función mientras estamos pasando el parámetro por referencia? en C ++

Por ejemplo, cuando intento declarar una función como:

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

Cuando hago esto, da un error:

error C2440: 'argumento predeterminado': no ​​se puede convertir de 'const int' a 'unsigned long &' Una referencia que no sea 'const' no se puede vincular a un valor que no sea l


96
Afortunadamente, no estamos sujetos a la guía de estilo de Google.

23
"No hagas esto. La guía de estilo de Google (y otras) prohíben el pase no constante por referencia" Creo que se sabe que las guías de estilo contienen muchas partes subjetivas. Este parece uno de ellos.
Johannes Schaub - litb

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

7
@jeffamaphone: la Guía de estilo de Google también dice que no se deben usar transmisiones. ¿Sugeriría evitarlos también?
rlbond

10
Un martillo es una herramienta horrible para colocar un destornillador, pero es bastante útil con clavos. El hecho de que pueda hacer un mal uso de una función solo debe advertirle sobre posibles peligros, pero no prohibir su uso.
David Rodríguez - dribeas

Respuestas:


102

Puede hacerlo para una referencia constante, pero no para una no constante. Esto se debe a que C ++ no permite que un temporal (el valor predeterminado en este caso) se vincule a una referencia no constante.

Una forma de evitar esto sería usar una instancia real como predeterminada:

static int AVAL = 1;

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

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

pero esto tiene un uso práctico muy limitado.


si lo hago constante, ¿me permitirá pasar una dirección diferente a la función? ¿O la dirección del Estado será siempre 0 y por tanto carente de significado?

Si está utilizando referencias, no está pasando direcciones.

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

1
si no aborda lo que realmente se está pasando?

2
@Sony Una referencia. Es retorcido pensar en ella como una dirección. Si desea una dirección, use un puntero.

32

Ya se ha dicho en uno de los comentarios directos a su respuesta, pero solo para expresarlo oficialmente. Lo que quieres usar es una sobrecarga:

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

El uso de sobrecargas de funciones también tiene beneficios adicionales. En primer lugar, puede predeterminar cualquier argumento que desee:

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...

También es posible redefinir argumentos predeterminados para funciones virtuales en la clase derivada, lo que evita la sobrecarga:

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
}

Solo hay una situación en la que realmente se necesita usar un valor predeterminado, y es en un constructor. No es posible llamar a un constructor desde otro, por lo que esta técnica no funciona en ese caso.


19
Algunas personas piensan que un parámetro predeterminado es menos horrible que una multiplicación gigante de sobrecargas.
Mr. Boy

2
Quizás al final, menciones: d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'
Pietro

4
En la última declaración: desde C ++ 11 en adelante, puede usar constructores delegados para llamar a un constructor desde otro para evitar la duplicación de código.
Fred Schoen

25

Todavía existe la antigua forma de C de proporcionar argumentos opcionales: un puntero que puede ser NULL cuando no está presente:

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

Me gusta mucho este método, muy corto y sencillo. Súper práctico si a veces quieres devolver información adicional, estadísticas, etc. que normalmente no necesitas.
uLoop

11

Esta pequeña plantilla te ayudará a:

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

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

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

private:
    T mValue;
};

Entonces podrás:

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

¿De dónde viene la instanciación de ByRefvivir, en cuanto a memoria? ¿No es un objeto temporal que se destruiría al dejar algún alcance (como el constructor)?
Andrew Cheong

1
@AndrewCheong Toda su intención es construirse en el lugar y destruirse cuando la línea esté completa. Es un medio de exponer una referencia durante la duración de una llamada para que se pueda proporcionar un parámetro predeterminado incluso cuando se espera una referencia. Este código se utiliza en un proyecto activo y funciona como se esperaba.
Mike Weir

7

No, no es posible.

Pasar por referencia implica que la función puede cambiar el valor del parámetro. Si la persona que llama no proporciona el parámetro y proviene de la constante predeterminada, ¿qué se supone que debe cambiar la función?


3
La forma tradicional de FORTRAN habría sido cambiar el valor de 0, pero eso no sucede en C ++.
David Thornley

7

Hay dos razones para pasar un argumento por referencia: (1) para el rendimiento (en cuyo caso desea pasar por referencia constante) y (2) porque necesita la capacidad de cambiar el valor del argumento dentro de la función.

Dudo mucho que pasar un largo tiempo sin firmar en arquitecturas modernas te esté frenando demasiado. Así que supongo que tiene la intención de cambiar el valor de Statedentro del método. El compilador se queja porque la constante 0no se puede cambiar, ya que es un rvalue ("non-lvalue" en el mensaje de error) e inmutable (const en el mensaje de error).

En pocas palabras, desea un método que pueda cambiar el argumento pasado, pero de forma predeterminada desea pasar un argumento que no puede cambiar.

Dicho de otra manera, las no constreferencias deben referirse a variables reales. El valor predeterminado en la función signatura ( 0) no es una variable real. Te encuentras con el mismo problema que:

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

Si realmente no tiene la intención de cambiar Statedentro del método, simplemente puede cambiarlo a un const ULONG&. Pero no obtendrá un gran beneficio de rendimiento de eso, por lo que recomendaría cambiarlo a uno que no sea de referencia ULONG. Me doy cuenta de que ya está devolviendo a ULONG, y tengo la astuta sospecha de que su valor es el valor de Statedespués de las modificaciones necesarias. En cuyo caso simplemente declararía el método así:

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

Por supuesto, no estoy muy seguro de qué estás escribiendo ni hacia dónde. Pero esa es otra pregunta para otro momento.


6

No puede usar un literal constante para un parámetro predeterminado por la misma razón que no puede usar uno como parámetro para la llamada de función. Los valores de referencia deben tener una dirección, los valores de referencias constantes no necesitan (es decir, pueden ser valores r o literales constantes).

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

foo(0); // compiler error.

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

bar(0); // ok.

Asegúrese de que su valor predeterminado tenga una dirección y esté bien.

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;
   }
}

He usado este patrón varias veces donde tenía un parámetro que podría ser una variable o nulo. El enfoque habitual es que el usuario pase un puntero, en este caso. Pasan un puntero NULL si no quieren que usted complete el valor. Me gusta el enfoque de objeto nulo. Facilita la vida de las personas que llaman sin complicar terriblemente el código de la persona que llama.


En mi humilde opinión, este estilo es bastante "maloliente". La única vez que un argumento predeterminado es realmente justificable es cuando se usa en un constructor. En todos los demás casos, las sobrecargas de funciones proporcionan exactamente la misma semántica sin ninguno de los otros problemas asociados con los valores predeterminados.
Richard Corden

1

Creo que no, y la razón es que los valores predeterminados se evalúan como constantes y los valores pasados ​​por referencia deben poder cambiar, a menos que también lo declare como referencia constante.


2
Los valores predeterminados no se "evalúan como constantes".

1

Otra forma podría ser la siguiente:

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

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

entonces son posibles las siguientes llamadas:

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;
}

Funciona. Básicamente, es para acceder al valor en la referencia para verificar la referencia que apunta NULL (lógicamente no hay una referencia NULL, solo que lo que apuntes es NULL). Encima, si está usando alguna biblioteca, que opera con "referencias", entonces generalmente habrá algunas API como "isNull ()" para hacer lo mismo con las variables de referencia específicas de la biblioteca. Y se sugiere utilizar esas API en tales casos.
parasrish

1

En el caso de OO ... Decir que una Clase dada tiene y "Predeterminado" significa que este (valor) Predeterminado debe declararse de forma adicional y luego puede usarse como Parámetro predeterminado, por ejemplo:

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

En tu método ...

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

Esta solución es bastante adecuada ya que en este caso, la "Paginación global predeterminada" es un único valor de "referencia". También tendrá el poder de cambiar los valores predeterminados en tiempo de ejecución, como una configuración de "nivel global", por ejemplo: preferencias de navegación de paginación del usuario, etc.


1

Es posible con el calificador const para el estado:

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

Es inútil e incluso ridículo pasar un tiempo largo como una referencia constante y no logra lo que quiere el OP.
Jim Balter

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

0

También hay un truco bastante sucio para esto:

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

En este caso tienes que llamarlo con std::move:

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

Es solo una solución divertida, ¡ no recomiendo totalmente su uso en código real!


0

Tengo una solución para esto, vea el siguiente ejemplo sobre el valor predeterminado para int&:

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

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

}

Puede hacerlo para cualquier tipo de datos trivial que desee, como bool, double...


0

Defina 2 funciones de sobrecarga.

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

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

-3

virtual const ULONG Write (ULONG & State = 0, secuencia bool = verdadera);

La respuesta es bastante simple y no soy tan bueno para explicar, pero si desea pasar un valor predeterminado a un parámetro no constante que probablemente se modificará en esta función, debe usarlo así:

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

3
Desreferenciar un puntero NULL es ilegal. Esto puede funcionar en algunos casos, pero es ilegal. Lea más aquí parashift.com/c++-faq-lite/references.html#faq-8.7
Spo1ler
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.