Valeur par défaut d'un paramètre lors du passage par référence en C ++


117

Est-il possible de donner une valeur par défaut à un paramètre d'une fonction pendant que nous passons le paramètre par référence. en C ++

Par exemple, lorsque j'essaye de déclarer une fonction comme:

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

Quand je fais cela, cela donne une erreur:

erreur C2440: 'argument par défaut': impossible de convertir de 'const int' en 'unsigned long &' Une référence qui n'est pas à 'const' ne peut pas être liée à une non-lvalue


96
Heureusement, nous ne sommes pas liés par le guide de style de Google.

23
"Ne faites pas cela. Google style guide (et d'autres) interdisent le passage par référence non const" Je pense que les guides de style sont connus pour contenir de nombreuses parties subjectives. Cela ressemble à l'un d'eux.
Johannes Schaub - litb le

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

7
@jeffamaphone: Google Style Guide dit également de ne pas utiliser de flux. Proposerez-vous également de les éviter?
rlbond

10
Un marteau est un outil horrible pour placer un tournevis, mais il est tout à fait utilisable avec des clous. Le fait que vous puissiez abuser d'une fonctionnalité ne devrait que vous avertir des écueils possibles, mais pas interdire son utilisation.
David Rodríguez - dribeas

Réponses:


103

Vous pouvez le faire pour une référence const, mais pas pour une référence non const. Cela est dû au fait que C ++ ne permet pas à un temporaire (la valeur par défaut dans ce cas) d'être lié à une référence non const.

Une solution consiste à utiliser une instance réelle par défaut:

static int AVAL = 1;

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

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

mais c'est d'une utilité pratique très limitée.


si je le fais const, me permettra-t-il de passer une adresse différente à la fonction? ou l'adresse de l'Etat sera-t-elle toujours 0 et donc dénuée de sens?

Si vous utilisez des références, vous ne transmettez pas d'adresses.

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

1
sinon adresse ce qui est réellement passé?

2
@Sony Une référence. Il est faux de penser à cela comme une adresse. Si vous voulez une adresse, utilisez un pointeur.

33

Cela a déjà été dit dans l'un des commentaires directs à votre réponse, mais simplement pour le déclarer officiellement. Ce que vous voulez utiliser est une surcharge:

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

L'utilisation de surcharges de fonctions présente également des avantages supplémentaires. Tout d'abord, vous pouvez par défaut n'importe quel argument que vous souhaitez:

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

Il est également possible de redéfinir les arguments par défaut des fonctions virtuelles dans la classe dérivée, ce qui évite la surcharge:

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
}

Il n'y a qu'une seule situation où une valeur par défaut doit vraiment être utilisée, et c'est sur un constructeur. Il n'est pas possible d'appeler un constructeur à partir d'un autre, et donc cette technique ne fonctionne pas dans ce cas.


19
Certaines personnes pensent qu'un paramètre par défaut est moins horrible qu'une multiplication géante de surcharges.
M. Boy

2
Peut-être à la fin, vous mentez: d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'
Pietro

4
Sur la dernière instruction: à partir de C ++ 11, vous pouvez utiliser des constructeurs de délégation pour appeler un constructeur à partir de l'autre afin d'éviter la duplication de code.
Fred Schoen

25

Il existe toujours l'ancienne façon C de fournir des arguments optionnels: un pointeur qui peut être NULL lorsqu'il n'est pas présent:

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

J'aime beaucoup cette méthode, très courte et simple. Super pratique si parfois vous souhaitez renvoyer des informations supplémentaires, des statistiques, etc. dont vous n'avez généralement pas besoin.
uLoop

11

Ce petit modèle vous aidera:

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

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

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

private:
    T mValue;
};

Ensuite, vous pourrez:

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

D'où vient l'instanciation du ByReflive, en mémoire? N'est-ce pas un objet temporaire qui serait détruit en quittant une certaine portée (comme le constructeur)?
Andrew Cheong

1
@AndrewCheong Son objectif entier est d'être construit sur place et détruit lorsque la ligne est terminée. C'est un moyen d'exposer une référence pendant la durée d'un appel afin qu'un paramètre par défaut puisse être fourni même lorsqu'il attend une référence. Ce code est utilisé dans un projet actif et fonctionne comme prévu.
Mike Weir

7

Non, ce n'est pas possible.

Le passage par référence implique que la fonction peut changer la valeur du paramètre. Si le paramètre n'est pas fourni par l'appelant et provient de la constante par défaut, quelle est la fonction censée changer?


3
La méthode FORTRAN traditionnelle aurait été de changer la valeur de 0, mais cela ne se produit pas en C ++.
David Thornley

7

Il y a deux raisons de passer un argument par référence: (1) pour les performances (auquel cas vous voulez passer par référence const) et (2) parce que vous avez besoin de la possibilité de changer la valeur de l'argument à l'intérieur de la fonction.

Je doute fort que passer un temps non signé sur les architectures modernes vous ralentisse trop. Je suppose donc que vous avez l'intention de changer la valeur de l' Stateintérieur de la méthode. Le compilateur se plaint parce que la constante 0ne peut pas être modifiée, car il s'agit d'une rvalue ("non-lvalue" dans le message d'erreur) et non modifiable ( constdans le message d'erreur).

En termes simples, vous voulez une méthode qui peut changer l'argument passé, mais par défaut, vous voulez passer un argument qui ne peut pas changer.

Pour le dire autrement, les non- constréférences doivent se référer à des variables réelles. La valeur par défaut de la fonction signature ( 0) n'est pas une vraie variable. Vous rencontrez le même problème 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 vous n'avez pas vraiment l'intention de modifier Statela méthode, vous pouvez simplement la changer en un fichier const ULONG&. Mais vous n'obtiendrez pas un grand avantage en termes de performances, donc je recommanderais de le remplacer par une non-référence ULONG. Je remarque que vous retournez déjà un ULONG, et j'ai un soupçon sournois que sa valeur est la valeur de Stateaprès toutes les modifications nécessaires. Dans ce cas, je déclarerais simplement la méthode comme suit:

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

Bien sûr, je ne suis pas tout à fait sûr de ce que vous écrivez ou vers où. Mais c'est une autre question pour une autre fois.


6

Vous ne pouvez pas utiliser un littéral constant pour un paramètre par défaut pour la même raison que vous ne pouvez pas en utiliser un comme paramètre pour l'appel de fonction. Les valeurs de référence doivent avoir une adresse, les valeurs de références constantes n'ont pas besoin (c'est-à-dire qu'elles peuvent être des valeurs r ou des littéraux constants).

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

foo(0); // compiler error.

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

bar(0); // ok.

Assurez-vous que votre valeur par défaut a une adresse et tout va 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;
   }
}

J'ai utilisé ce modèle à quelques reprises où j'avais un paramètre qui pouvait être une variable ou une valeur nulle. L'approche régulière consiste à faire passer l'utilisateur dans un pointeur dans ce cas. Ils transmettent un pointeur NULL s'ils ne veulent pas que vous remplissiez la valeur. J'aime l'approche d'objet nul. Cela facilite la vie des appelants sans compliquer terriblement le code de l'appelé.


IMHO, ce style est assez "malodorant". Le seul moment où un argument par défaut est vraiment justifiable, c'est lorsqu'il est utilisé dans un constructeur. Dans tous les autres cas, les surcharges de fonctions fournissent exactement la même sémantique sans aucun des autres problèmes associés aux valeurs par défaut.
Richard Corden

1

Je ne pense pas, et la raison en est que les valeurs par défaut sont évaluées en constantes et que les valeurs passées par référence doivent pouvoir changer, à moins que vous ne le déclariez également comme référence constante.


2
Les valeurs par défaut ne sont pas «évaluées comme des constantes».

1

Une autre façon pourrait être la suivante:

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

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

alors les appels suivants sont possibles:

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

Ça marche. Fondamentalement, il s'agit d'accéder à la valeur à la référence pour vérifier la référence de pointage NULL (logiquement, il n'y a pas de référence NULL, seulement ce que vous pointez est NULL). Au sommet, si vous utilisez une bibliothèque, qui fonctionne sur des "références", alors il y aura généralement des API comme "isNull ()" pour faire de même pour les variables de référence spécifiques à la bibliothèque. Et il est suggéré d'utiliser ces API dans de tels cas.
parasrish

1

En cas de OO ... Dire qu'une classe donnée a et "Default" signifie que cette valeur par défaut (valeur) doit être déclarée en conséquence et alors peut être usd comme paramètre par défaut ex:

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

Sur votre méthode ...

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

Cette solution est tout à fait adaptée car dans ce cas, "Global default Pagination" est une seule valeur de "référence". Vous aurez également le pouvoir de modifier les valeurs par défaut au moment de l'exécution comme une configuration de "niveau global" ex: préférences de navigation de la pagination de l'utilisateur et etc.


1

C'est possible avec le qualificatif const pour l'état:

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

Il est inutile et même ridicule de passer un long moment en tant que référence const, et n'atteint pas ce que l'OP veut.
Jim Balter

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

0

Il y a aussi un truc assez sale pour cela:

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

Dans ce cas, vous devez l'appeler avec std::move:

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

Ce n'est qu'une solution de contournement amusante, je ne recommande absolument pas de l'utiliser dans du code réel!


0

J'ai une solution de contournement pour cela, voir l'exemple suivant sur la valeur par défaut pour int&:

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

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

}

Vous pouvez le faire pour tout type de données trivial que vous souhaitez, tel que bool, double...


0

Définissez 2 fonctions de surcharge.

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, bool sequence = true);

La réponse est assez simple et je ne suis pas très douée pour expliquer mais si vous voulez passer une valeur par défaut à un paramètre non-const qui sera probablement modifié dans cette fonction, c'est de l'utiliser comme ceci:

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

3
Le déréférencement d'un pointeur NULL est illégal. Cela peut fonctionner dans certains cas, mais c'est illégal. En savoir plus ici parashift.com/c++-faq-lite/references.html#faq-8.7
Spo1ler
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.