Nilai default ke parameter saat meneruskan referensi di C ++

117

Apakah mungkin untuk memberikan nilai default ke parameter suatu fungsi saat kita meneruskan parameter dengan referensi. di C ++

Misalnya, ketika saya mencoba mendeklarasikan fungsi seperti:

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

Ketika saya melakukan ini, itu memberikan kesalahan:

kesalahan C2440: 'argumen default': tidak dapat mengonversi dari 'const int' menjadi 'unsigned long &' Referensi yang bukan untuk 'const' tidak dapat terikat ke nilai non-l

Drew Noakes
sumber
96
Untungnya, kami tidak terikat oleh panduan gaya Google.
23
"Jangan lakukan ini. Panduan gaya Google (dan lainnya) melarang lewat referensi non-const" Saya pikir panduan gaya diketahui mengandung banyak bagian subjektif. Ini terlihat seperti salah satunya.
Johannes Schaub - litb
13
WxWidgets style guide says "don't use templates" and they have good reasons<- screw std :: vector, saya katakan
Johannes Schaub - litb
7
@jeffamaphone: Panduan Gaya Google juga menyarankan untuk tidak menggunakan aliran. Apakah Anda akan menyarankan untuk menghindarinya juga?
rlbond
10
Palu adalah alat yang mengerikan untuk memasang obeng, tetapi cukup bisa digunakan dengan paku. Fakta bahwa Anda dapat menyalahgunakan fitur seharusnya hanya memperingatkan Anda tentang kemungkinan jebakan tetapi tidak melarang penggunaannya.
David Rodríguez - dribeas

Jawaban:

103

Anda bisa melakukannya untuk referensi const, tetapi tidak untuk non-const. Ini karena C ++ tidak mengizinkan sementara (nilai default dalam kasus ini) untuk terikat ke referensi non-const.

Salah satu cara untuk melakukannya adalah dengan menggunakan instance aktual sebagai default:

static int AVAL = 1;

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

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

tetapi penggunaan praktis ini sangat terbatas.


sumber
jika saya membuatnya menjadi const, akankah saya dapat mengirimkan alamat yang berbeda ke fungsi tersebut? atau akankah alamat Negara selalu 0 dan tidak berarti?
Jika Anda menggunakan referensi, Anda tidak memberikan alamat.
3
boost :: array to the rescue void f (int & x = boost :: array <int, 1> () [0]) {..} :)
Johannes Schaub - litb
1
jika tidak membahas apa yang sebenarnya dilewatkan?
2
@Sony A referensi. Wriong untuk menganggapnya sebagai sebuah alamat. Jika Anda menginginkan alamat, gunakan penunjuk.
33

Itu telah dikatakan di salah satu komentar langsung ke jawaban Anda, tetapi hanya untuk menyatakannya secara resmi. Yang ingin Anda gunakan adalah kelebihan beban:

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

Menggunakan kelebihan fungsi juga memiliki manfaat tambahan. Pertama, Anda dapat menetapkan default argumen apa pun yang Anda inginkan:

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

Juga dimungkinkan untuk mendefinisikan ulang argumen default ke fungsi virtual di kelas turunan, yang menghindari overloading:

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
}

Hanya ada satu situasi di mana default benar-benar perlu digunakan, dan itu ada di konstruktor. Tidak mungkin memanggil satu konstruktor dari yang lain, sehingga teknik ini tidak berfungsi dalam kasus itu.

Richard Corden
sumber
19
Beberapa orang berpikir default-param kurang mengerikan dari perkalian raksasa kelebihan beban.
Tn. Boy
2
Mungkin di akhir, Anda menyebutkan: d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'
Pietro
4
Pada pernyataan terakhir: dari C ++ 11 dan seterusnya, Anda dapat menggunakan konstruktor delegasi untuk memanggil satu konstruktor dari yang lain untuk menghindari duplikasi kode.
Fred Schoen
25

Masih ada cara C lama untuk menyediakan argumen opsional: pointer yang bisa berupa NULL jika tidak ada:

void write( int *optional = 0 ) {
    if (optional) *optional = 5;
}
David Rodríguez - dribeas
sumber
Saya sangat suka metode ini, sangat singkat dan sederhana. Sangat praktis jika terkadang Anda ingin mengembalikan beberapa info tambahan, statistik, dll. Yang biasanya tidak Anda perlukan.
uLoop
11

Template kecil ini akan membantu Anda:

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

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

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

private:
    T mValue;
};

Kemudian Anda akan dapat:

virtual const ULONG Write(ULONG &State = ByRef<ULONG>(0), bool sequence = true);
Mike Weir
sumber
Di manakah contoh ByReflive, berdasarkan ingatan? Bukankah itu objek sementara yang akan hancur setelah meninggalkan beberapa ruang lingkup (seperti konstruktor)?
Andrew Cheong
1
@AndrewCheong Tujuan keseluruhannya adalah untuk dibangun di tempat dan dihancurkan ketika jalur selesai. Ini adalah cara untuk mengekspos referensi selama panggilan berlangsung sehingga parameter default dapat diberikan bahkan ketika mengharapkan referensi. Kode ini digunakan dalam proyek aktif dan berfungsi seperti yang diharapkan.
Mike Weir
7

Tidak, itu tidak mungkin.

Melewati referensi menyiratkan bahwa fungsi tersebut mungkin mengubah nilai parameter. Jika parameter tidak disediakan oleh pemanggil dan berasal dari konstanta default, fungsi apa yang harus diubah?

NascarEd
sumber
3
Cara FORTRAN tradisional adalah mengubah nilai 0, tetapi itu tidak terjadi di C ++.
David Thornley
7

Ada dua alasan untuk meneruskan argumen dengan referensi: (1) untuk performa (dalam hal ini Anda ingin meneruskan referensi const) dan (2) karena Anda memerlukan kemampuan untuk mengubah nilai argumen di dalam fungsi.

Saya sangat meragukan bahwa melewatkan waktu yang lama pada arsitektur modern terlalu memperlambat Anda. Jadi saya berasumsi bahwa Anda bermaksud mengubah nilai Statedi dalam metode. Kompilator mengeluh karena konstanta 0tidak dapat diubah, karena ini adalah nilai r ("non-lvalue" dalam pesan kesalahan) dan tidak dapat diubah ( constdalam pesan kesalahan).

Sederhananya, Anda menginginkan metode yang dapat mengubah argumen yang diteruskan, tetapi secara default Anda ingin meneruskan argumen yang tidak dapat diubah.

Dengan kata lain, non- constreferensi harus mengacu pada variabel aktual. Nilai default di function signature ( 0) bukanlah variabel nyata. Anda mengalami masalah yang sama seperti:

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

Jika Anda tidak benar-benar berniat untuk mengubah Statedi dalam metode ini, Anda dapat mengubahnya menjadi const ULONG&. Tetapi Anda tidak akan mendapatkan keuntungan kinerja yang besar dari itu, jadi saya akan merekomendasikan untuk mengubahnya menjadi non-referensi ULONG. Saya perhatikan bahwa Anda telah mengembalikan a ULONG, dan saya curiga bahwa nilainya adalah nilai Statesetelah modifikasi yang diperlukan. Dalam hal ini saya hanya akan mendeklarasikan metode sebagai berikut:

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

Tentu saja, saya tidak yakin apa yang Anda tulis atau ke mana. Tapi itu pertanyaan lain untuk lain waktu.

Max Lybbert
sumber
6

Anda tidak dapat menggunakan literal konstan untuk parameter default karena alasan yang sama Anda tidak dapat menggunakan satu sebagai parameter untuk pemanggilan fungsi. Nilai referensi harus memiliki alamat, nilai referensi konstan tidak perlu (yaitu dapat berupa nilai r atau literal konstan).

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

foo(0); // compiler error.

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

bar(0); // ok.

Pastikan nilai default Anda memiliki alamat dan Anda baik-baik saja.

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

Saya telah menggunakan pola ini beberapa kali di mana saya memiliki parameter yang bisa berupa variabel atau nol. Pendekatan biasa adalah meminta pengguna untuk memasukkan pointer ke kasus ini. Mereka mengirimkan pointer NULL jika mereka tidak ingin Anda mengisi nilainya. Saya suka pendekatan objek null. Itu membuat hidup penelepon lebih mudah tanpa memperumit kode panggilan.

deft_code
sumber
IMHO, gaya ini cukup "bau". Satu-satunya saat argumen default benar-benar dapat dibenarkan adalah ketika digunakan dalam konstruktor. Dalam setiap kasus lainnya, kelebihan beban fungsi menyediakan semantik yang persis sama tanpa masalah lain yang terkait dengan default.
Richard Corden
1

Saya kira tidak, dan alasannya adalah bahwa nilai default dievaluasi menjadi konstanta dan nilai yang diteruskan oleh referensi harus dapat berubah, kecuali jika Anda juga menyatakannya sebagai referensi konstan.

ilya n.
sumber
2
Nilai default tidak "dievaluasi sebagai konstanta".
1

Cara lain bisa jadi sebagai berikut:

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

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

maka panggilan berikut dimungkinkan:

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
sumber
1
void f(const double& v = *(double*) NULL)
{
  if (&v == NULL)
    cout << "default" << endl;
  else
    cout << "other " << v << endl;
}
Waxrat
sumber
Berhasil. Pada dasarnya, ini adalah untuk mengakses nilai-pada-referensi untuk memeriksa referensi pengarah NULL (secara logis tidak ada referensi NULL, hanya yang Anda tunjuk adalah NULL). Di atas, jika Anda menggunakan beberapa perpustakaan, yang beroperasi pada "referensi", maka umumnya akan ada beberapa API seperti "isNull ()" untuk melakukan hal yang sama untuk variabel referensi khusus perpustakaan. Dan disarankan untuk menggunakan API tersebut dalam kasus seperti itu.
parasrish
1

Dalam kasus OO ... Untuk mengatakan bahwa Kelas yang Diberikan memiliki dan "Default" berarti Default ini (nilai) harus dideklarasikan secara langsung dan kemudian dapat digunakan sebagai Parameter Default misalnya:

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

Tentang Metode Anda ...

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

Solusi ini sangat cocok karena dalam kasus ini, "Penomoran halaman default global" adalah nilai "referensi" tunggal. Anda juga dapat mengubah nilai default pada waktu proses seperti konfigurasi "level-gobal", misalnya: preferensi navigasi penomoran halaman pengguna, dll.

wdavilaneto.dll
sumber
1

Itu mungkin dengan kualifikasi const untuk Negara:

virtual const ULONG Write(const ULONG &State = 0, bool sequence = true);
vic
sumber
Tidak ada gunanya dan bahkan konyol melewatkan waktu lama sebagai ref const, dan tidak mencapai apa yang diinginkan OP.
Jim Balter
0
void revealSelection(const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, bool revealExtent = false);
Abhijeet Kandalkar
sumber
0

Ada juga trik kotor untuk ini:

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

Dalam hal ini Anda harus memanggilnya dengan std::move:

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

Ini hanya beberapa solusi lucu, saya sama sekali tidak merekomendasikannya menggunakan kode nyata!

avtomaton.dll
sumber
0

Saya memiliki solusi untuk ini, lihat contoh berikut tentang nilai default untuk int&:

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

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

}

Anda dapat melakukannya untuk tipe data sepele apa pun yang Anda inginkan, seperti bool, double...

Omar Natour
sumber
0

Tentukan 2 fungsi kelebihan beban.

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

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

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

Jawabannya cukup sederhana dan saya tidak begitu pandai menjelaskan tetapi jika Anda ingin meneruskan nilai default ke parameter non-const yang mungkin akan dimodifikasi dalam fungsi ini adalah menggunakannya seperti ini:

virtual const ULONG Write(ULONG &State = *(ULONG*)0, bool sequence =
> true);
bogdan
sumber
3
Dereferensi penunjuk NULL adalah ilegal. Ini mungkin berhasil dalam beberapa kasus, tetapi ilegal. Baca lebih lanjut di sini parashift.com/c++-faq-lite/references.html#faq-8.7
Spo1ler