Wartość domyślna do parametru podczas przekazywania przez odwołanie w C ++

117

Czy jest możliwe nadanie wartości domyślnej parametrowi funkcji, gdy przekazujemy parametr przez odniesienie? w C ++

Na przykład, gdy próbuję zadeklarować funkcję taką jak:

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

Kiedy to robię, wyświetla błąd:

błąd C2440: „domyślny argument”: nie można przekonwertować z „const int” na „unsigned long” & „Odwołanie, które nie jest do„ const ”, nie może być powiązane z wartością inną niż l

Drew Noakes
źródło
96
Na szczęście nie jesteśmy związani przewodnikiem stylistycznym Google.
23
„Nie rób tego. Przewodnik stylów Google (i inne) zakazuje przekazywania przez odniesienie non-const”. Myślę, że przewodniki stylów zawierają wiele subiektywnych części. To wygląda na jeden z nich.
Johannes Schaub - litb
13
WxWidgets style guide says "don't use templates" and they have good reasons<- screw std :: vector, mówię
Johannes Schaub - litb
7
@jeffamaphone: Przewodnik po stylu Google mówi również, aby nie używać strumieni. Czy zaproponujesz też ich unikanie?
rlbond
10
Młotek to okropne narzędzie do umieszczenia śrubokręta, ale jest całkiem użyteczny w przypadku gwoździ. Fakt, że możesz nadużywać funkcji, powinien tylko ostrzegać o możliwych pułapkach, ale nie powinien zabraniać jej używania.
David Rodríguez - dribeas

Odpowiedzi:

103

Możesz to zrobić dla odwołania do stałej, ale nie dla odniesienia innego niż stała. Dzieje się tak, ponieważ C ++ nie pozwala na tymczasowe (w tym przypadku wartość domyślną) powiązanie z odwołaniem niebędącym stałym.

Jednym ze sposobów byłoby użycie rzeczywistej instancji jako domyślnej:

static int AVAL = 1;

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

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

ale ma to bardzo ograniczone praktyczne zastosowanie.


źródło
jeśli zrobię to const, czy pozwoli mi to przekazać inny adres do funkcji? czy też adres Stanu będzie zawsze równy 0 i tak bez znaczenia?
Jeśli używasz referencji, nie przekazujesz adresów.
3
boost :: array to the rescue void f (int & x = boost :: array <int, 1> () [0]) {..} :)
Johannes Schaub - litb
1
jeśli nie dotyczy tego, co faktycznie jest przekazywane?
2
@Sony Odniesienie. Trudno myśleć o nim jak o adresie. Jeśli potrzebujesz adresu, użyj wskaźnika.
33

Zostało to już powiedziane w jednym z bezpośrednich komentarzy do Twojej odpowiedzi, ale tylko po to, aby oficjalnie to stwierdzić. To, czego chcesz użyć, to przeciążenie:

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

Używanie przeciążeń funkcji ma również dodatkowe zalety. Po pierwsze, możesz ustawić domyślny dowolny argument:

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

Możliwe jest również przedefiniowanie domyślnych argumentów na funkcje wirtualne w klasie pochodnej, co pozwala uniknąć przeciążenia:

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
}

Jest tylko jedna sytuacja, w której naprawdę trzeba użyć wartości domyślnej, a jest to konstruktor. Nie jest możliwe wywołanie jednego konstruktora z innego, więc ta technika nie działa w tym przypadku.

Richarda Cordena
źródło
19
Niektórzy uważają, że domyślny parametr jest mniej straszny niż gigantyczne zwielokrotnienie przeciążeń.
Mr. Boy
2
Może na koniec wspomniałeś: d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'
Pietro
4
W ostatniej instrukcji: począwszy od języka C ++ 11, można używać konstruktorów delegujących do wywoływania jednego konstruktora z drugiego, aby uniknąć duplikowania kodu.
Fred Schoen
25

Wciąż istnieje stary sposób C na dostarczanie opcjonalnych argumentów: wskaźnik, który może mieć wartość NULL, gdy nie jest obecny:

void write( int *optional = 0 ) {
    if (optional) *optional = 5;
}
David Rodríguez - dribeas
źródło
Bardzo podoba mi się ta metoda, bardzo krótka i prosta. Bardzo praktyczne, jeśli czasami chcesz zwrócić dodatkowe informacje, statystyki itp., Których zwykle nie potrzebujesz.
uLoop
11

Ten mały szablon pomoże ci:

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

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

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

private:
    T mValue;
};

Wtedy będziesz mógł:

virtual const ULONG Write(ULONG &State = ByRef<ULONG>(0), bool sequence = true);
Mike Weir
źródło
Gdzie jest tworzenie instancji na ByRefżywo, jeśli chodzi o pamięć? Czy nie jest to obiekt tymczasowy, który zostałby zniszczony po opuszczeniu jakiegoś zakresu (jak konstruktor)?
Andrew Cheong,
1
@AndrewCheong Jego celem jest zbudowanie na miejscu i zniszczenie po ukończeniu linii. Jest to sposób na ujawnienie odwołania na czas trwania wywołania, dzięki czemu można podać parametr domyślny, nawet jeśli oczekuje się odwołania. Ten kod jest używany w aktywnym projekcie i działa zgodnie z oczekiwaniami.
Mike Weir
7

Nie, to niemożliwe.

Przekazywanie przez odwołanie oznacza, że ​​funkcja może zmienić wartość parametru. Jeśli parametr nie jest dostarczany przez obiekt wywołujący i pochodzi z domyślnej stałej, co ma zmienić funkcja?

NascarEd
źródło
3
Tradycyjnym sposobem FORTRAN byłaby zmiana wartości 0, ale tak się nie dzieje w C ++.
David Thornley
7

Istnieją dwa powody, dla których należy przekazać argument przez odwołanie: (1) ze względu na wydajność (w takim przypadku chcesz przekazać przez odwołanie do stałej) i (2), ponieważ potrzebujesz możliwości zmiany wartości argumentu wewnątrz funkcji.

Wątpię, czy zbyt długi czas poświęcony nowoczesnym architekturom zbytnio Cię spowolni. Zakładam więc, że zamierzasz zmienić wartość Statewewnątrz metody. Kompilator narzeka, ponieważ stałej 0nie można zmienić, ponieważ jest to wartość r („non-lvalue” w komunikacie o błędzie) i niezmienna (const w komunikacie o błędzie).

Mówiąc najprościej, potrzebujesz metody, która może zmienić przekazany argument, ale domyślnie chcesz przekazać argument, którego nie można zmienić.

Inaczej mówiąc, elementy niebędące constreferencjami muszą odnosić się do rzeczywistych zmiennych. Wartość domyślna funkcji signature ( 0) nie jest rzeczywistą zmienną. Masz ten sam problem, co:

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

Jeśli w rzeczywistości nie zamierzasz zmieniać Statew metodzie, możesz po prostu zmienić ją na const ULONG&. Ale nie odniesiesz z tego dużej korzyści z wydajności, więc poleciłbym zmienić to na nie-odniesienie ULONG. Zauważyłem, że zwracasz już a ULONG, i mam podstępne podejrzenie, że jego wartość jest wartością Statepo wszelkich potrzebnych modyfikacjach. W takim przypadku po prostu zadeklarowałbym metodę jako taką:

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

Oczywiście nie jestem pewien, co piszesz ani gdzie. Ale to kolejne pytanie na inny czas.

Max Lybbert
źródło
6

Nie można użyć stałego literału dla parametru domyślnego z tego samego powodu, dla którego nie można użyć go jako parametru do wywołania funkcji. Wartości odniesienia muszą mieć adres, stałe wartości odniesienia nie muszą (tj. Mogą to być wartości r lub stałe literały).

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

foo(0); // compiler error.

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

bar(0); // ok.

Upewnij się, że wartość domyślna ma adres i wszystko w porządku.

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

Użyłem tego wzorca kilka razy, gdy miałem parametr, który mógł być zmienną lub zerową. W takim przypadku zwykłym podejściem jest podanie przez użytkownika wskaźnika. Przekazują wskaźnik NULL, jeśli nie chcą, abyś wpisał wartość. Lubię podejście zerowe. Ułatwia życie dzwoniącym bez strasznego komplikowania kodu wywołującego.

deft_code
źródło
IMHO, ten styl jest dość „śmierdzący”. Jedynym przypadkiem, gdy domyślny argument jest naprawdę uzasadniony, jest użycie go w konstruktorze. W każdym innym przypadku przeciążenia funkcji zapewniają dokładnie tę samą semantykę, bez żadnych innych problemów związanych z wartościami domyślnymi.
Richard Corden
1

Myślę, że nie, a powodem jest to, że wartości domyślne są oceniane jako stałe, a wartości przekazywane przez odniesienie muszą być w stanie zmienić, chyba że zadeklarujesz również, że jest to stałe odniesienie.

ilya n.
źródło
2
Wartości domyślne nie są „oceniane jako stałe”.
1

Innym sposobem może być:

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

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

wtedy możliwe są następujące połączenia:

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
źródło
1
void f(const double& v = *(double*) NULL)
{
  if (&v == NULL)
    cout << "default" << endl;
  else
    cout << "other " << v << endl;
}
Waxrat
źródło
To działa. Zasadniczo jest to uzyskanie dostępu do wartości odniesienia w celu sprawdzenia odniesienia wskazującego na NULL (logicznie nie ma odniesienia do wartości NULL, tylko to, co wskazujesz, to NULL). Na koniec, jeśli używasz jakiejś biblioteki, która operuje na „referencjach”, to na ogół będzie kilka funkcji API, takich jak „isNull ()”, które będą robić to samo dla zmiennych referencyjnych specyficznych dla biblioteki. W takich przypadkach zaleca się używanie tych interfejsów API.
parasrish
1

W przypadku OO ... Powiedzieć, że dana klasa ma i „Domyślna” oznacza, że ​​ta wartość domyślna (wartość) musi być zadeklarowana odpowiednio, a następnie może być USD jako parametr domyślny, np .:

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

O Twojej metodzie ...

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

To rozwiązanie jest całkiem odpowiednie, ponieważ w tym przypadku „Globalna domyślna paginacja” jest pojedynczą wartością „odniesienia”. Będziesz mieć również możliwość zmiany wartości domyślnych w czasie wykonywania, takich jak konfiguracja „na poziomie globalnym”, np. Preferencje nawigacji po stronicowaniu użytkowników itp.

wdavilaneto
źródło
1

Jest to możliwe z kwalifikatorem const dla State:

virtual const ULONG Write(const ULONG &State = 0, bool sequence = true);
ofiara
źródło
Przekazywanie długości jako const ref jest bezcelowe, a nawet śmieszne, i nie osiąga tego, czego chce OP.
Jim Balter
0
void revealSelection(const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, bool revealExtent = false);
Abhijeet Kandalkar
źródło
0

Jest na to dość brudna sztuczka:

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

W takim przypadku musisz zadzwonić za pomocą std::move:

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

To tylko zabawne obejście, całkowicie nie polecam używania go w prawdziwym kodzie!

avtomaton
źródło
0

Mam obejście tego problemu, zobacz następujący przykład dotyczący wartości domyślnej dla int&:

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

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

}

Można to zrobić za każdym trywialny typu danych, które chcesz, takich jak bool, double...

Omar Natour
źródło
0

Zdefiniuj 2 funkcje przeciążenia.

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

virtual const ULONG Write(bool sequence = true)
{
    int State = 0;
    return Write(State, sequence);
}
Zhang
źródło
-3

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

Odpowiedź jest dość prosta i nie jestem zbyt dobry w wyjaśnianiu, ale jeśli chcesz przekazać domyślną wartość do parametru innego niż const, który prawdopodobnie zostanie zmodyfikowany w tej funkcji, użyj go w następujący sposób:

virtual const ULONG Write(ULONG &State = *(ULONG*)0, bool sequence =
> true);
bogdan
źródło
3
Dereferencja wskaźnika NULL jest niedozwolona. To może działać w niektórych przypadkach, ale jest to nielegalne. Przeczytaj więcej tutaj parashift.com/c++-faq-lite/references.html#faq-8.7
Spo1ler