Standardwert für einen Parameter beim Übergeben als Referenz in C ++


116

Ist es möglich, einem Parameter einer Funktion einen Standardwert zuzuweisen, während wir den Parameter als Referenz übergeben? in C ++

Zum Beispiel, wenn ich versuche, eine Funktion wie folgt zu deklarieren:

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

Wenn ich das mache, gibt es einen Fehler:

Fehler C2440: 'Standardargument': Konvertiert nicht von 'const int' in 'unsigned long &' Ein Verweis, der nicht auf 'const' lautet, kann nicht an einen Nicht-l-Wert gebunden werden


96
Zum Glück sind wir nicht an den Google Style Guide gebunden.

23
"Tun Sie dies nicht. Google Style Guide (und andere) verbieten das Weitergeben von nicht konstanten Referenzen" Ich denke, dass Style Guides bekanntermaßen viele subjektive Teile enthalten. Das sieht aus wie einer von ihnen.
Johannes Schaub - litb

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

7
@jeffamaphone: Im Google Style Guide wird außerdem empfohlen, keine Streams zu verwenden. Schlagen Sie vor, sie auch zu vermeiden?
Rlbond

10
Ein Hammer ist ein schreckliches Werkzeug, um einen Schraubenzieher zu platzieren, aber er kann durchaus mit Nägeln verwendet werden. Die Tatsache, dass Sie eine Funktion missbrauchen können, sollte Sie nur vor möglichen Fallstricken warnen, deren Verwendung jedoch nicht verbieten.
David Rodríguez - Dribeas

Antworten:


101

Sie können dies für eine konstante Referenz tun, jedoch nicht für eine nicht konstante. Dies liegt daran, dass C ++ nicht zulässt, dass ein temporärer Wert (in diesem Fall der Standardwert) an eine nicht konstante Referenz gebunden wird.

Eine Möglichkeit, dies zu umgehen, besteht darin, eine tatsächliche Instanz als Standard zu verwenden:

static int AVAL = 1;

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

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

Dies ist jedoch von sehr begrenztem praktischem Nutzen.


Wenn ich es const mache, kann ich dann eine andere Adresse an die Funktion übergeben? oder wird die Adresse des Staates immer 0 und so bedeutungslos sein?

Wenn Sie Referenzen verwenden, geben Sie keine Adressen weiter.

3
boost :: array zur Rettungslücke f (int & x = boost :: array <int, 1> () [0]) {..} :)
Johannes Schaub - litb

1
Wenn nicht Adressen, was wird tatsächlich bestanden?

2
@Sony Eine Referenz. Es ist falsch, es als Adresse zu betrachten. Wenn Sie eine Adresse wünschen, verwenden Sie einen Zeiger.

32

Es wurde bereits in einem der direkten Kommentare zu Ihrer Antwort gesagt, aber nur um es offiziell zu erklären. Was Sie verwenden möchten, ist eine Überlastung:

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

Die Verwendung von Funktionsüberladungen bietet zusätzliche Vorteile. Erstens können Sie jedes gewünschte Argument als Standard festlegen:

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

Es ist auch möglich, Standardargumente für virtuelle Funktionen in abgeleiteten Klassen neu zu definieren, wodurch eine Überladung vermieden wird:

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
}

Es gibt nur eine Situation, in der ein Standardwert wirklich verwendet werden muss, und zwar auf einem Konstruktor. Es ist nicht möglich, einen Konstruktor von einem anderen aufzurufen, daher funktioniert diese Technik in diesem Fall nicht.


19
Einige Leute denken, ein Standardparameter sei weniger schrecklich als eine riesige Vervielfachung von Überlastungen.
Mr. Boy

2
Vielleicht erwähnen Sie am Ende: d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'
Pietro

4
Zur letzten Anweisung: Ab C ++ 11 können Sie delegierende Konstruktoren verwenden, um einen Konstruktor vom anderen aufzurufen, um Codeduplizierungen zu vermeiden.
Fred Schoen

25

Es gibt immer noch die alte C-Methode, optionale Argumente bereitzustellen: Ein Zeiger, der NULL sein kann, wenn er nicht vorhanden ist:

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

Ich mag diese Methode sehr, sehr kurz und einfach. Super praktisch, wenn Sie manchmal zusätzliche Informationen, Statistiken usw. zurückgeben möchten, die Sie normalerweise nicht benötigen.
uLoop

11

Diese kleine Vorlage hilft Ihnen:

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

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

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

private:
    T mValue;
};

Dann können Sie:

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

Woher kommt die Instanziierung des Lebens in Bezug auf ByRefdie Erinnerung? Ist es nicht ein temporäres Objekt, das beim Verlassen eines Bereichs zerstört wird (wie der Konstruktor)?
Andrew Cheong

1
@AndrewCheong Seine gesamte Absicht ist es, vor Ort gebaut und zerstört zu werden, wenn die Linie fertig ist. Auf diese Weise können Sie eine Referenz für die Dauer eines Aufrufs verfügbar machen, sodass ein Standardparameter auch dann bereitgestellt werden kann, wenn eine Referenz erwartet wird. Dieser Code wird in einem aktiven Projekt verwendet und funktioniert wie erwartet.
Mike Weir

7

Nein, es ist nicht möglich.

Das Übergeben als Referenz impliziert, dass die Funktion den Wert des Parameters ändern kann. Wenn der Parameter nicht vom Aufrufer bereitgestellt wird und von der Standardkonstante stammt, welche Funktion soll sich ändern?


3
Der traditionelle FORTRAN-Weg wäre gewesen, den Wert 0 zu ändern, aber das passiert in C ++ nicht.
David Thornley

7

Es gibt zwei Gründe, ein Argument als Referenz zu übergeben: (1) für die Leistung (in diesem Fall möchten Sie als Konstantenreferenz übergeben) und (2), weil Sie die Fähigkeit benötigen, den Wert des Arguments innerhalb der Funktion zu ändern.

Ich bezweifle sehr, dass das Übergeben eines unsignierten Longs für moderne Architekturen Sie zu sehr verlangsamt. Ich gehe also davon aus, dass Sie beabsichtigen, den Wert Stateinnerhalb der Methode zu ändern . Der Compiler beschwert sich, weil die Konstante 0nicht geändert werden kann, da es sich um einen r-Wert ("nicht-l-Wert" in der Fehlermeldung) und einen unveränderlichen Wert handelt (const in der Fehlermeldung) ist.

Einfach ausgedrückt, Sie möchten eine Methode, die das übergebene Argument ändern kann, aber standardmäßig möchten Sie ein Argument übergeben, das sich nicht ändern kann.

Anders ausgedrückt, Nichtreferenzen constmüssen sich auf tatsächliche Variablen beziehen. Der Standardwert in der Funktionssignatur ( 0) ist keine echte Variable. Sie haben das gleiche Problem wie:

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

Wenn Sie nicht beabsichtigen, Statedie Methode zu ändern , können Sie sie einfach in a ändern const ULONG&. Aber Sie werden keinen großen Leistungsvorteil davon erhalten, daher würde ich empfehlen, es in eine Nichtreferenz zu ändern ULONG. Ich stelle fest, dass Sie a bereits zurückgeben ULONG, und ich habe den Verdacht, dass sein Wert der Wert Statenach erforderlichen Änderungen ist. In diesem Fall würde ich die Methode einfach so deklarieren:

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

Natürlich bin ich mir nicht ganz sicher, was Sie schreiben oder wohin. Aber das ist eine andere Frage für eine andere Zeit.


6

Sie können kein konstantes Literal für einen Standardparameter verwenden, aus demselben Grund, aus dem Sie keines als Parameter für den Funktionsaufruf verwenden können. Referenzwerte müssen eine Adresse haben, konstante Referenzwerte müssen keine Adresse haben (dh sie können r-Werte oder konstante Literale sein).

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

foo(0); // compiler error.

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

bar(0); // ok.

Stellen Sie sicher, dass Ihr Standardwert eine Adresse hat und es Ihnen gut geht.

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

Ich habe dieses Muster einige Male verwendet, als ich einen Parameter hatte, der eine Variable oder Null sein konnte. Der reguläre Ansatz besteht darin, dass der Benutzer einen Zeiger übergibt. Dies ist der Fall. Sie übergeben einen NULL-Zeiger, wenn Sie den Wert nicht eingeben sollen. Ich mag es, den Objektansatz auf Null zu setzen. Dies erleichtert dem Anrufer das Leben, ohne den Angerufenencode zu erschweren.


IMHO, dieser Stil ist ziemlich "stinkend". Ein Standardargument ist nur dann wirklich gerechtfertigt, wenn es in einem Konstruktor verwendet wird. In jedem anderen Fall bieten Funktionsüberladungen genau die gleiche Semantik ohne die anderen mit Standardwerten verbundenen Probleme.
Richard Corden

1

Ich denke nicht, und der Grund dafür ist, dass Standardwerte als Konstanten ausgewertet werden und Werte, die als Referenz übergeben werden, sich ändern können müssen, es sei denn, Sie deklarieren sie auch als konstante Referenz.


2
Standardwerte werden nicht "als Konstanten ausgewertet".

1

Ein anderer Weg könnte der folgende sein:

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

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

dann sind folgende aufrufe möglich:

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

Es klappt. Grundsätzlich ist es der Zugriff auf den Referenzwert zum Überprüfen der NULL-Referenz (logischerweise gibt es keine NULL-Referenz, nur das, was Sie zeigen, ist NULL). Wenn Sie eine Bibliothek verwenden, die mit "Referenzen" arbeitet, gibt es im Allgemeinen einige APIs wie "isNull ()", um dasselbe für die bibliotheksspezifischen Referenzvariablen zu tun. In solchen Fällen wird empfohlen, diese APIs zu verwenden.
Parasrish

1

Im Fall von OO ... Zu sagen, dass eine bestimmte Klasse hat und "Standard" bedeutet, dass dieser Standard (Wert) entsprechend deklariert werden muss und dann als Standardparameter verwendet werden kann, z.

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

Auf Ihrer Methode ...

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

Diese Lösung ist sehr gut geeignet, da in diesem Fall "Globale Standardpaginierung" ein einzelner "Referenzwert" ist. Sie haben auch die Möglichkeit, Standardwerte zur Laufzeit zu ändern, z. B. eine Konfiguration auf "globaler Ebene", z. B.: Navigationseinstellungen für die Benutzer-Paginierung usw.


1

Es ist möglich mit const Qualifier für State:

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

Es ist sinnlos und sogar lächerlich, eine lange Zeit als Schiedsrichter zu bestehen und nicht das zu erreichen, was das OP will.
Jim Balter

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

0

Dafür gibt es auch einen ziemlich schmutzigen Trick:

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

In diesem Fall müssen Sie es aufrufen mit std::move:

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

Es ist nur eine lustige Problemumgehung, ich empfehle die Verwendung in echtem Code überhaupt nicht!


0

Ich habe eine Problemumgehung dafür, siehe das folgende Beispiel zum Standardwert für int&:

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

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

}

Sie können es für jeden trivialen Datentyp Sie wünschen, wie bool, double...


-3

virtuelle Konstante ULONG Write (ULONG & State = 0, Bool-Sequenz = true);

Die Antwort ist recht einfach und ich kann nicht so gut erklären, aber wenn Sie einen Standardwert an einen nicht konstanten Parameter übergeben möchten, der wahrscheinlich in dieser Funktion geändert wird, verwenden Sie ihn wie folgt:

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

3
Das Dereferenzieren eines NULL-Zeigers ist unzulässig. Dies kann in einigen Fällen funktionieren, ist aber illegal. Lesen Sie hier mehr parashift.com/c++-faq-lite/references.html#faq-8.7
Spo1ler
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.