Valor padrão para um parâmetro ao passar por referência em C ++

116

É possível dar um valor padrão a um parâmetro de uma função enquanto passamos o parâmetro por referência. em C ++

Por exemplo, quando tento declarar uma função como:

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

Quando faço isso, ocorre um erro:

erro C2440: 'argumento padrão': não é possível converter de 'const int' para 'unsigned long &' Uma referência que não é para 'const' não pode ser associada a um não-lvalue

Drew Noakes
fonte
96
Felizmente, não estamos limitados pelo guia de estilo do Google.
23
"Não faça isso. O guia de estilo do Google (e outros) proíbe a passagem não const por referência" Acho que os guias de estilo contêm muitas partes subjetivas. Este parece um deles.
Johannes Schaub - litb
13
WxWidgets style guide says "don't use templates" and they have good reasons<- parafuso std :: vector, eu digo
Johannes Schaub - litb
7
@jeffamaphone: O Guia de estilo do Google também diz para não usar streams. Você vai sugerir evitá-los também?
rlbond
10
Um martelo é uma ferramenta horrível para colocar uma chave de fenda, mas pode ser usada com pregos. O fato de você poder usar indevidamente um recurso deve apenas alertá-lo sobre possíveis armadilhas, mas não proibir seu uso.
David Rodríguez - dribeas

Respostas:

102

Você pode fazer isso para uma referência const, mas não para uma não const. Isso ocorre porque o C ++ não permite que um temporário (o valor padrão neste caso) seja vinculado a uma referência não constante.

Uma maneira de contornar isso seria usar uma instância real como padrão:

static int AVAL = 1;

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

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

mas isso é de uso prático muito limitado.


fonte
se eu torná-lo constante, ele me permitirá passar um endereço diferente para a função? ou o endereço do Estado será sempre 0 e, portanto, sem sentido?
Se você está usando referências, não está passando endereços.
3
boost :: array para o resgate void f (int & x = boost :: array <int, 1> () [0]) {..} :)
Johannes Schaub - litb
1
se não aborda o que está realmente sendo aprovado?
2
@Sony Uma referência. É wriong pensar nisso como um endereço. Se você quiser um endereço, use um ponteiro.
32

Isso já foi dito em um dos comentários diretos à sua resposta, mas apenas para declará-lo oficialmente. O que você deseja usar é uma sobrecarga:

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

Usar sobrecargas de funções também traz benefícios adicionais. Em primeiro lugar, você pode usar como padrão qualquer argumento que desejar:

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

Também é possível redefinir os argumentos padrão para funções virtuais na classe derivada, o que evita a 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
}

Existe apenas uma situação em que um padrão realmente precisa ser usado, e é em um construtor. Não é possível chamar um construtor de outro e, portanto, essa técnica não funciona nesse caso.

Richard Corden
fonte
19
Algumas pessoas pensam que um parâmetro padrão é menos horrível do que uma multiplicação gigante de sobrecargas.
Mr. Boy
2
Talvez no final, você diga: d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'
Pietro
4
Na última instrução: do C ++ 11 em diante, você pode usar construtores de delegação para chamar um construtor do outro para evitar a duplicação de código.
Fred Schoen
25

Ainda existe a velha maneira C de fornecer argumentos opcionais: um ponteiro que pode ser NULL quando não está presente:

void write( int *optional = 0 ) {
    if (optional) *optional = 5;
}
David Rodríguez - dribeas
fonte
Gosto muito desse método, muito curto e simples. Super prático se às vezes você deseja retornar algumas informações adicionais, estatísticas, etc. que você normalmente não precisa.
uLoop
11

Este pequeno modelo irá ajudá-lo a:

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

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

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

private:
    T mValue;
};

Então você será capaz de:

virtual const ULONG Write(ULONG &State = ByRef<ULONG>(0), bool sequence = true);
Mike Weir
fonte
De onde vem a instanciação de ByRefviver, em termos de memória? Não é um objeto temporário que seria destruído ao deixar algum escopo (como o construtor)?
Andrew Cheong
1
@AndrewCheong Todo o seu objetivo é ser construído no local e destruído quando a linha estiver completa. É um meio de expor uma referência durante uma chamada para que um parâmetro padrão possa ser fornecido mesmo quando se espera uma referência. Este código é usado em um projeto ativo e funciona conforme o esperado.
Mike Weir
7

Não, não é possível.

A passagem por referência implica que a função pode alterar o valor do parâmetro. Se o parâmetro não for fornecido pelo chamador e vier da constante padrão, o que a função deve mudar?

NascarEd
fonte
3
A maneira tradicional do FORTRAN seria alterar o valor de 0, mas isso não acontece em C ++.
David Thornley
7

Há duas razões para passar um argumento por referência: (1) para desempenho (nesse caso, você deseja passar por referência const) e (2) porque você precisa da capacidade de alterar o valor do argumento dentro da função.

Eu duvido muito que passar um longo tempo sem sinal em arquiteturas modernas esteja atrasando você demais. Portanto, estou assumindo que você pretende alterar o valor de Statedentro do método. O compilador está reclamando porque a constante 0não pode ser alterada, pois é um rvalue ("não-lvalue" na mensagem de erro) e imutável ( constna mensagem de erro).

Simplificando, você quer um método que pode mudar o argumento passado, mas por padrão você deseja passar um argumento que não pode mudar.

Em outras palavras, as não constreferências têm que se referir a variáveis ​​reais. O valor padrão na assinatura da função ( 0) não é uma variável real. Você está tendo o mesmo 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

Se você realmente não pretende alterar Statedentro do método, pode simplesmente alterá-lo para um const ULONG&. Mas você não terá um grande benefício de desempenho com isso, então eu recomendo alterá-lo para um não-referência ULONG. Percebi que você já está retornando a ULONGe tenho uma leve suspeita de que seu valor é o valor de Stateapós quaisquer modificações necessárias. Nesse caso, eu simplesmente declararia o método assim:

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

Claro, não tenho certeza do que você está escrevendo ou para onde. Mas essa é outra questão para outra hora.

Max Lybbert
fonte
6

Você não pode usar um literal constante para um parâmetro padrão pelo mesmo motivo que você não pode usar um como um parâmetro para a chamada de função. Os valores de referência devem ter um endereço, os valores de referência constantes não precisam (ou seja, podem ser valores r ou literais constantes).

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

foo(0); // compiler error.

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

bar(0); // ok.

Certifique-se de que seu valor padrão tem um endereço e você está bem.

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

Usei esse padrão algumas vezes onde tinha um parâmetro que poderia ser uma variável ou nulo. A abordagem normal é fazer com que o usuário passe um ponteiro, neste caso. Eles passam um ponteiro NULL se não quiserem que você preencha o valor. Eu gosto de abordagem de objeto nulo. Facilita a vida do chamador sem complicar terrivelmente o código do receptor.

deft_code
fonte
IMHO, esse estilo é bastante "fedorento". A única vez que um argumento padrão é realmente justificável é quando ele é usado em um construtor. Em todos os outros casos, as sobrecargas de função fornecem exatamente a mesma semântica, sem nenhum dos outros problemas associados aos padrões.
Richard Corden
1

Acho que não, e o motivo é que os valores padrão são avaliados como constantes e os valores passados ​​por referência devem poder mudar, a menos que você também declare que é uma referência constante.

ilya n.
fonte
2
Os valores padrão não são "avaliados como constantes".
1

Outra forma poderia ser a seguinte:

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

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

então as seguintes chamadas são possíveis:

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"
JJTh
fonte
1
void f(const double& v = *(double*) NULL)
{
  if (&v == NULL)
    cout << "default" << endl;
  else
    cout << "other " << v << endl;
}
Waxrat
fonte
Funciona. Basicamente, é para acessar o valor-na-referência para verificar a referência de apontamento NULL (logicamente não há referência NULL, apenas que o que você aponta é NULL). Além disso, se você estiver usando alguma biblioteca, que opera em "referências", geralmente haverá algumas APIs como "isNull ()" para fazer o mesmo para as variáveis ​​de referência específicas da biblioteca. E é sugerido o uso dessas APIs em tais casos.
parasrish de
1

No caso de OO ... Dizer que uma determinada classe tem e "Padrão" significa que esse padrão (valor) deve ser declarado em seguida e pode ser usado como um parâmetro padrão ex:

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

No seu método ...

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

Esta solução é bastante adequada, pois, neste caso, "Paginação padrão global" é um único valor de "referência". Você também terá o poder de alterar os valores padrão em tempo de execução, como uma configuração de "nível gobal", por exemplo: preferências de navegação de paginação do usuário e etc.

wdavilaneto
fonte
1

É possível com o qualificador const para o estado:

virtual const ULONG Write(const ULONG &State = 0, bool sequence = true);
vic
fonte
É inútil e até ridículo passar um long como um ref ref, e não alcançar o que o OP deseja.
Jim Balter,
0
void revealSelection(const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, bool revealExtent = false);
Abhijeet Kandalkar
fonte
0

Também há um truque bastante sujo para isso:

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

Neste caso, você deve chamá-lo com std::move :

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

É apenas uma alternativa engraçada, eu totalmente não recomendo usar em código real!

Avtomaton
fonte
0

Eu tenho uma solução alternativa para isso, veja o seguinte exemplo sobre o valor padrão para int&:

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

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

}

Você pode fazer isso para qualquer tipo de dados trivial que desejar, como bool, double...

Omar Natour
fonte
0

Defina 2 funções 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);
}
Zhang
fonte
-3

gravação ULONG const virtual (ULONG & State = 0, sequência booleana = verdadeiro);

A resposta é bastante simples e não sou muito bom em explicar, mas se você quiser passar um valor padrão para um parâmetro não const que provavelmente será modificado nesta função é usá-lo assim:

virtual const ULONG Write(ULONG &State = *(ULONG*)0, bool sequence =
> true);
Bogdan
fonte
3
Cancelar a referência de um ponteiro NULL é ilegal. Isso pode funcionar em alguns casos, mas é ilegal. Leia mais aqui parashift.com/c++-faq-lite/references.html#faq-8.7
Spo1ler