ค่าดีฟอลต์ของพารามิเตอร์ขณะส่งผ่านโดยการอ้างอิงใน C ++


118

เป็นไปได้หรือไม่ที่จะให้ค่าเริ่มต้นแก่พารามิเตอร์ของฟังก์ชันในขณะที่เรากำลังส่งผ่านพารามิเตอร์โดยการอ้างอิง ใน C ++

ตัวอย่างเช่นเมื่อฉันพยายามประกาศฟังก์ชันเช่น:

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

เมื่อฉันทำสิ่งนี้จะทำให้เกิดข้อผิดพลาด:

ข้อผิดพลาด C2440: 'อาร์กิวเมนต์เริ่มต้น': ไม่สามารถแปลงจาก 'const int' เป็น 'ยาวที่ไม่ได้ลงนาม &' การอ้างอิงที่ไม่เป็น 'const' ไม่สามารถผูกไว้กับค่าที่ไม่ใช่ lvalue


96
ด้วยความเมตตาเราไม่ได้ผูกพันตามคำแนะนำสไตล์ Google

23
"อย่าทำเช่นนี้ Google style guide (และอื่น ๆ ) ห้ามการอ้างอิงแบบ non-const" ฉันคิดว่าคำแนะนำสไตล์เป็นที่รู้กันว่ามีหลายส่วนที่เป็นอัตนัย ดูเหมือนว่าหนึ่งในนั้น
Johannes Schaub - litb

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

7
@jeffamaphone: Google Style Guide ยังบอกว่าห้ามใช้สตรีม คุณจะแนะนำให้หลีกเลี่ยงด้วยหรือไม่?
rlbond

10
ค้อนเป็นเครื่องมือที่น่ากลัวในการวางไขควง แต่มันใช้ได้กับตะปู ความจริงที่ว่าคุณสามารถใช้คุณสมบัติในทางที่ผิดควรเตือนคุณเกี่ยวกับข้อผิดพลาดที่อาจเกิดขึ้นเท่านั้น แต่ไม่ได้ห้ามการใช้งาน
David Rodríguez - dribeas

คำตอบ:


104

คุณสามารถทำได้เพื่อการอ้างอิง const แต่ไม่ใช่สำหรับการอ้างอิงที่ไม่ใช่ const เนื่องจาก C ++ ไม่อนุญาตให้มีการเชื่อมโยงชั่วคราว (ค่าเริ่มต้นในกรณีนี้) กับการอ้างอิงที่ไม่ใช่ const

วิธีหนึ่งคือการใช้อินสแตนซ์จริงเป็นค่าเริ่มต้น:

static int AVAL = 1;

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

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

แต่เป็นการใช้งานจริงที่ จำกัด มาก


ถ้าฉันตั้งค่าเป็น const จะอนุญาตให้ส่งที่อยู่อื่นไปยังฟังก์ชันได้หรือไม่ หรือที่อยู่ของรัฐจะเป็น 0 เสมอและไม่มีความหมาย?

หากคุณใช้ข้อมูลอ้างอิงแสดงว่าคุณไม่ได้ส่งที่อยู่

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

1
หากไม่ได้ระบุสิ่งที่ส่งผ่านจริง?

2
@ โซนี่อ้างอิง เป็นเรื่องน่าคิดที่จะคิดว่าเป็นที่อยู่ หากคุณต้องการที่อยู่ให้ใช้ตัวชี้

34

มีการกล่าวไว้ในความคิดเห็นโดยตรงสำหรับคำตอบของคุณแล้ว แต่เพียงเพื่อระบุอย่างเป็นทางการ สิ่งที่คุณต้องการใช้คือโอเวอร์โหลด:

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

การใช้ฟังก์ชันโอเวอร์โหลดยังมีประโยชน์เพิ่มเติม ประการแรกคุณสามารถเริ่มต้นอาร์กิวเมนต์ที่คุณต้องการ:

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

นอกจากนี้ยังสามารถกำหนดอาร์กิวเมนต์เริ่มต้นใหม่ให้กับฟังก์ชันเสมือนในคลาสที่ได้รับซึ่งการโอเวอร์โหลดจะหลีกเลี่ยง:

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
}

มีเพียงสถานการณ์เดียวที่จำเป็นต้องใช้ค่าดีฟอลต์จริง ๆ และอยู่บนตัวสร้าง เป็นไปไม่ได้ที่จะเรียกตัวสร้างหนึ่งจากอีกคนหนึ่งดังนั้นเทคนิคนี้จึงใช้ไม่ได้ในกรณีนั้น


19
บางคนคิดว่าพารามิเตอร์เริ่มต้นนั้นน่ากลัวน้อยกว่าการโอเวอร์โหลดแบบคูณยักษ์
คุณบอย

2
ในตอนท้ายคุณอาจกล่าวถึง: d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'
Pietro

4
ในคำสั่งสุดท้าย: ตั้งแต่ C ++ 11 เป็นต้นไปคุณสามารถใช้ตัวสร้างการมอบหมายเพื่อเรียกตัวสร้างหนึ่งตัวจากอีกตัวหนึ่งเพื่อหลีกเลี่ยงการทำซ้ำรหัส
Fred Schoen

25

ยังคงมีวิธี C แบบเก่าในการให้อาร์กิวเมนต์ที่เป็นทางเลือก: ตัวชี้ที่สามารถเป็น NULL ได้เมื่อไม่มีอยู่:

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

ฉันชอบวิธีนี้มากสั้นและเรียบง่าย ใช้งานได้จริงหากบางครั้งคุณต้องการส่งคืนข้อมูลสถิติและอื่น ๆ เพิ่มเติมที่คุณไม่ต้องการ
uLoop

11

เทมเพลตเล็ก ๆ นี้จะช่วยคุณ:

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

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

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

private:
    T mValue;
};

จากนั้นคุณจะสามารถ:

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

การสร้างอินสแตนซ์ของการByRefถ่ายทอดสดที่ชาญฉลาดอยู่ที่ไหน? ไม่ใช่วัตถุชั่วคราวที่จะถูกทำลายเมื่อออกจากขอบเขตบางอย่าง (เช่นตัวสร้าง)?
Andrew Cheong

1
@AndrewCheong เจตนาทั้งหมดคือการสร้างในจุดและทำลายเมื่อเส้นเสร็จสมบูรณ์ เป็นวิธีการเปิดเผยข้อมูลอ้างอิงสำหรับช่วงเวลาของการโทรเพื่อให้สามารถระบุพารามิเตอร์เริ่มต้นได้แม้ว่าจะต้องการข้อมูลอ้างอิงก็ตาม รหัสนี้ใช้ในโปรเจ็กต์ที่ใช้งานอยู่และทำงานตามที่คาดไว้
Mike Weir

7

ไม่เป็นไปไม่ได้

การส่งผ่านโดยการอ้างอิงหมายความว่าฟังก์ชันอาจเปลี่ยนค่าของพารามิเตอร์ หากผู้โทรไม่ได้ระบุพารามิเตอร์และมาจากค่าคงที่เริ่มต้นฟังก์ชันควรจะเปลี่ยนอะไร


3
วิธี FORTRAN แบบดั้งเดิมคือการเปลี่ยนค่าเป็น 0 แต่จะไม่เกิดขึ้นใน C ++
David Thornley

7

มีสองเหตุผลในการส่งผ่านอาร์กิวเมนต์โดยการอ้างอิง: (1) สำหรับประสิทธิภาพ (ซึ่งในกรณีนี้คุณต้องการส่งผ่านการอ้างอิง const) และ (2) เนื่องจากคุณต้องการความสามารถในการเปลี่ยนค่าของอาร์กิวเมนต์ภายในฟังก์ชัน

ฉันสงสัยเป็นอย่างยิ่งว่าการส่งสถาปัตยกรรมสมัยใหม่ที่ไม่ได้ลงนามมานานจะทำให้คุณช้าลงมากเกินไป ดังนั้นฉันสมมติว่าคุณตั้งใจจะเปลี่ยนค่าของStateภายในเมธอด คอมไพเลอร์กำลังบ่นเนื่องจาก0ไม่สามารถเปลี่ยนแปลงค่าคงที่ได้เนื่องจากเป็น rvalue ("non-lvalue" ในข้อความแสดงข้อผิดพลาด) และไม่สามารถเปลี่ยนแปลงได้ ( constในข้อความแสดงข้อผิดพลาด)

พูดง่ายๆก็คือคุณต้องการวิธีที่สามารถเปลี่ยนอาร์กิวเมนต์ที่ส่งผ่าน แต่โดยค่าเริ่มต้นคุณต้องการส่งผ่านอาร์กิวเมนต์ที่ไม่สามารถเปลี่ยนแปลงได้

กล่าวอีกนัยหนึ่งการไม่constอ้างอิงต้องอ้างถึงตัวแปรจริง ค่าเริ่มต้นในลายเซ็นฟังก์ชัน ( 0) ไม่ใช่ตัวแปรจริง คุณกำลังประสบปัญหาเดียวกันกับ:

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

หากคุณไม่ได้ตั้งใจจะเปลี่ยนStateภายในวิธีนี้คุณสามารถเปลี่ยนเป็นconst ULONG&ไฟล์. ULONGแต่คุณจะไม่ได้รับผลประโยชน์ใหญ่จากนั้นดังนั้นผมจะแนะนำให้เปลี่ยนไปไม่ใช่การอ้างอิง ฉันสังเกตเห็นว่าคุณส่งคืน a ULONGแล้วและฉันสงสัยอย่างลับๆว่าค่าของมันคือมูลค่าStateหลังจากการแก้ไขที่จำเป็น ซึ่งในกรณีนี้ฉันจะประกาศวิธีการดังนี้:

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

แน่นอนฉันไม่ค่อยแน่ใจว่าคุณกำลังเขียนอะไรหรือเขียนถึงที่ไหน แต่นั่นเป็นอีกคำถามหนึ่ง


6

คุณไม่สามารถใช้ลิเทอรัลคงที่สำหรับพารามิเตอร์ดีฟอลต์ได้ด้วยเหตุผลเดียวกับที่คุณไม่สามารถใช้หนึ่งเป็นพารามิเตอร์ในการเรียกฟังก์ชัน ค่าอ้างอิงต้องมีแอดเดรสค่าการอ้างอิงคงที่ไม่จำเป็นต้อง (กล่าวคือสามารถเป็นค่า r หรือค่าคงที่ตามตัวอักษร)

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

foo(0); // compiler error.

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

bar(0); // ok.

ตรวจสอบให้แน่ใจว่าค่าเริ่มต้นของคุณมีที่อยู่และคุณสบายดี

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

ฉันเคยใช้รูปแบบนี้สองสามครั้งโดยที่ฉันมีพารามิเตอร์ที่อาจเป็นตัวแปรหรือโมฆะ วิธีการปกติคือให้ผู้ใช้ส่งผ่านตัวชี้ซึ่งเป็นกรณีนี้ พวกเขาส่งผ่านตัวชี้ NULL หากพวกเขาไม่ต้องการให้คุณกรอกค่า ฉันชอบวิธีวัตถุว่าง ทำให้ชีวิตของผู้โทรง่ายขึ้นโดยไม่ต้องใส่รหัสโทรให้ยุ่งยาก


IMHO สไตล์นี้ค่อนข้าง "เหม็น" ครั้งเดียวที่อาร์กิวเมนต์เริ่มต้นจะสมเหตุสมผลจริงๆคือเมื่อใช้ในตัวสร้าง ในกรณีอื่น ๆ ทุกฟังก์ชันโอเวอร์โหลดจะให้ความหมายเหมือนกันทุกประการโดยไม่มีปัญหาอื่น ๆ ที่เกี่ยวข้องกับค่าเริ่มต้น
Richard Corden

1

ฉันคิดว่าไม่และเหตุผลก็คือค่าเริ่มต้นจะถูกประเมินเป็นค่าคงที่และค่าที่ส่งผ่านการอ้างอิงจะต้องสามารถเปลี่ยนแปลงได้เว้นแต่คุณจะประกาศว่าเป็นการอ้างอิงคงที่


2
ค่าเริ่มต้นไม่ได้ถูก "ประเมินเป็นค่าคงที่"

1

อีกวิธีหนึ่งอาจเป็นดังต่อไปนี้:

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

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

จากนั้นสามารถโทรต่อไปนี้ได้:

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

มันได้ผล. โดยทั่วไปคือการเข้าถึง value-at-reference สำหรับการตรวจสอบการอ้างอิงการชี้ NULL (ในทางตรรกะไม่มีการอ้างอิง NULL แต่สิ่งที่คุณชี้เป็น NULL) หากคุณกำลังใช้ไลบรารีบางส่วนซึ่งทำงานเกี่ยวกับ "การอ้างอิง" โดยทั่วไปจะมี API บางตัวเช่น "isNull ()" สำหรับการทำเช่นเดียวกันสำหรับตัวแปรอ้างอิงเฉพาะของไลบรารี และขอแนะนำให้ใช้ API เหล่านั้นในกรณีเช่นนี้
Parasrish

1

ในกรณีของ OO ... หากต้องการบอกว่าคลาสที่กำหนดมีและ "ค่าเริ่มต้น" หมายความว่าค่าเริ่มต้น (ค่า) นี้จะต้องประกาศอย่างชัดเจนจากนั้นจึงอาจใช้ usd เป็นพารามิเตอร์เริ่มต้นเช่น:

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

ในวิธีการของคุณ ...

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

โซลูชันนี้ค่อนข้างเหมาะสมเนื่องจากในกรณีนี้ "การแบ่งหน้าเริ่มต้นทั่วโลก" คือค่า "อ้างอิง" เดียว นอกจากนี้คุณยังมีอำนาจในการเปลี่ยนค่าเริ่มต้นที่รันไทม์เช่นการกำหนดค่า "ระดับโกลเบิล" เช่นการกำหนดหน้าเพจของผู้ใช้และอื่น ๆ


1

เป็นไปได้ด้วย const qualifier สำหรับ State:

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

มันไม่มีจุดหมายและไร้สาระที่จะผ่านไปนานเท่า const ref และไม่บรรลุสิ่งที่ OP ต้องการ
Jim Balter


0

นอกจากนี้ยังมีเคล็ดลับที่ค่อนข้างสกปรกสำหรับสิ่งนี้:

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

ในกรณีนี้คุณต้องเรียกมันว่าstd::move:

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

มันเป็นวิธีแก้ปัญหาที่ตลกเท่านั้นฉันไม่แนะนำให้ใช้ในรหัสจริงโดยสิ้นเชิง!


0

ฉันมีวิธีแก้ปัญหานี้โปรดดูตัวอย่างต่อไปนี้เกี่ยวกับค่าเริ่มต้นสำหรับint&:

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

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

}

คุณสามารถทำมันสำหรับประเภทข้อมูลใด ๆ เล็ก ๆ น้อย ๆ ที่คุณต้องการเช่นbool, double...


0

กำหนด 2 ฟังก์ชั่นโอเวอร์โหลด

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

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

-3

การเขียน ULONG const เสมือน (ULONG & State = 0, ลำดับบูล = จริง);

คำตอบนั้นค่อนข้างง่ายและฉันอธิบายไม่ค่อยเก่ง แต่ถ้าคุณต้องการส่งค่าเริ่มต้นไปยังพารามิเตอร์ที่ไม่ใช่ const ซึ่งอาจจะได้รับการแก้ไขในฟังก์ชันนี้คือการใช้มันดังนี้:

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

3
การอ้างถึงตัวชี้ NULL ถือเป็นสิ่งผิดกฎหมาย ซึ่งอาจใช้ได้ผลในบางกรณี แต่ผิดกฎหมาย อ่านเพิ่มเติมที่นี่parashift.com/c++-faq-lite/references.html#faq-8.7
Spo1ler
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.