การใช้ 'const' สำหรับพารามิเตอร์ฟังก์ชัน


397

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

void SetValue(const bool b) { my_val_ = b; }

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

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

ไฟล์. h

void func(int n, long l);

ไฟล์. cpp

void func(const int n, const long l)

มีเหตุผลสำหรับสิ่งนี้หรือไม่? ดูเหมือนว่าผิดปกติเล็กน้อยสำหรับฉัน


ฉันไม่เห็นด้วย. ไฟล์. h จะต้องมีคำจำกัดความ const เช่นกัน ถ้าไม่เช่นนั้นถ้าพารามิเตอร์ const ถูกส่งผ่านไปยังฟังก์ชันคอมไพเลอร์จะสร้างข้อผิดพลาดเนื่องจากต้นแบบในไฟล์. h ไม่มีนิยาม const
selwyn

10
ฉันเห็นด้วย. :-) (ด้วยคำถามไม่ใช่ความคิดเห็นล่าสุด!) หากค่าไม่ควรมีการเปลี่ยนแปลงในเนื้อหาของฟังก์ชั่นสิ่งนี้สามารถช่วยหยุดข้อผิดพลาด == or = ข้อผิดพลาดคุณไม่ควรใส่ const ทั้งคู่ (ถ้า มันผ่านไปแล้วตามตัวอักษรคุณต้องเป็นอย่างอื่น) มันไม่ร้ายแรงพอที่จะพูดถึงมัน!
Chris Huang-Leaver

19
@selwyn: แม้ว่าคุณจะผ่าน const int ไปยังฟังก์ชันแม้ว่ามันจะถูกคัดลอก (เนื่องจากไม่ใช่การอ้างอิง) และดังนั้น const-ness ก็ไม่สำคัญ
jalf

1
การอภิปรายแบบเดียวกันนี้เกิดขึ้นในคำถามนี้: stackoverflow.com/questions/1554750/…
บางส่วน

5
ฉันรู้ว่าโพสต์นี้มีอายุเพียงไม่กี่ปี แต่ในฐานะโปรแกรมเมอร์ใหม่ฉันสงสัยคำถามนี้มากและฉันก็สะดุดกับการสนทนานี้ ในความคิดของฉันถ้าฟังก์ชั่นไม่ควรเปลี่ยนค่าไม่ว่าจะเป็นการอ้างอิงหรือคัดลอกค่า / วัตถุมันควรจะเป็น const ปลอดภัยกว่าใช้เอกสารเองและเป็นมิตรกับการดีบักมากขึ้น แม้แต่ฟังก์ชั่นที่ง่ายที่สุดซึ่งมีหนึ่งคำสั่งฉันยังคงใช้ const

คำตอบ:


187

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

ฉันเองมักจะไม่ใช้ const ยกเว้นพารามิเตอร์การอ้างอิงและตัวชี้ สำหรับวัตถุที่ถูกคัดลอกมันไม่สำคัญเลยแม้ว่ามันจะปลอดภัยกว่าเพราะมันเป็นสัญญาณความตั้งใจภายในฟังก์ชั่น มันเป็นสายการตัดสิน ฉันมักจะใช้ const_iterator แม้ว่าในขณะที่วนรอบบางสิ่งบางอย่างและฉันไม่ได้ตั้งใจที่จะแก้ไขมันดังนั้นฉันเดาว่าแต่ละคนของเขาเองตราบใดที่ความถูกต้องของ const สำหรับประเภทอ้างอิงนั้นได้รับการบำรุงรักษาอย่างจริงจัง


57
ฉันไม่เห็นด้วยกับส่วน 'สไตล์ที่ไม่ดี' การดร็อปconstจากฟังก์ชันต้นแบบมีข้อดีที่คุณไม่จำเป็นต้องแก้ไขไฟล์ส่วนหัวหากคุณตัดสินใจที่จะดรอปconstจากส่วนการนำไปปฏิบัติในภายหลัง
MichałGórny

4
"โดยส่วนตัวแล้วฉันมักจะไม่ใช้ const ยกเว้นพารามิเตอร์อ้างอิงและตัวชี้" บางทีคุณควรชี้แจงว่า "ฉันมักจะไม่ใช้ตัวระบุที่ไม่จำเป็นในการประกาศฟังก์ชัน แต่ใช้ในconstกรณีที่ทำให้เกิดความแตกต่างที่มีประโยชน์"
Deduplicator

3
ฉันไม่เห็นด้วยกับคำตอบนี้ ฉันเอียงวิธีอื่นและทำเครื่องหมายพารามิเตอร์constเมื่อทำได้ มันแสดงออกมากขึ้น เมื่อฉันอ่านรหัสของคนอื่นฉันใช้ตัวบ่งชี้เล็กน้อยเช่นนี้เพื่อตัดสินว่าพวกเขาสนใจเขียนโค้ดของตนควบคู่ไปกับสิ่งต่าง ๆ เช่นหมายเลขเวทย์มนตร์การแสดงความคิดเห็นและการใช้ตัวชี้ที่เหมาะสมเป็นต้น
Ultimater

4
int getDouble(int a){ ++a; return 2*a; }ลองสิ่งนี้ แน่นอนว่า++aไม่มีสิ่งใดที่จะทำที่นั่น แต่สามารถพบได้ในฟังก์ชั่นที่ยาวนานซึ่งเขียนโดยโปรแกรมเมอร์มากกว่าหนึ่งคนในช่วงเวลาที่ยาวนาน ฉันจะขอแนะนำให้เขียนที่จะสร้างรวบรวมข้อผิดพลาดเมื่อค้นพบint getDouble( const int a ){ //... } ++a;
dom_beau

3
มันเป็นเรื่องของผู้ที่ต้องการข้อมูลใด คุณจัดเตรียมพารามิเตอร์ตามค่าดังนั้นผู้เรียกไม่จำเป็นต้องรู้อะไรเกี่ยวกับสิ่งที่คุณทำ (ภายใน) ดังนั้นเขียนclass Foo { int multiply(int a, int b) const; }ในส่วนหัวของคุณ ในการดำเนินการของคุณคุณจะดูแลที่คุณสามารถสัญญาว่าจะไม่เปลี่ยนแปลงaและbเพื่อint Foo::multiply(const int a, const int b) const { }ทำให้ความรู้สึกที่นี่ (Sidenote: ทั้งผู้โทรและผู้ดูแลการดำเนินการเกี่ยวกับความจริงที่ว่าฟังก์ชั่นไม่เปลี่ยนแปลงFooวัตถุดังนั้น const ในตอนท้ายของการประกาศ)
CharonX

415

"const ไม่มีจุดหมายเมื่ออาร์กิวเมนต์ผ่านค่าเนื่องจากคุณจะไม่แก้ไขวัตถุของผู้โทร"

ไม่ถูกต้อง.

มันเกี่ยวกับการบันทึกรหัสของคุณและสมมติฐานของคุณ

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

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


41
เห็นด้วยอย่างสิ้นเชิง. มันคือทั้งหมดที่เกี่ยวกับการสื่อสารกับผู้คนและการ จำกัด สิ่งที่สามารถทำได้กับตัวแปรในสิ่งที่ควรทำ
Len Holgate

19
ฉันลงคะแนนครั้งนี้ลง ฉันคิดว่าคุณเจือจางสิ่งที่คุณพยายามระบุด้วย const เมื่อคุณใช้กับการส่งผ่านค่าอาร์กิวเมนต์อย่างง่าย
tonylo

26
ฉันโหวตให้คนนี้แล้ว การประกาศพารามิเตอร์ 'const' เพิ่มข้อมูลเชิงความหมายให้กับพารามิเตอร์ พวกเขาเน้นสิ่งที่ผู้เขียนต้นฉบับของรหัสตั้งใจไว้และสิ่งนี้จะช่วยบำรุงรักษารหัสเมื่อเวลาผ่านไป
Richard Corden

13
@tonylo: คุณเข้าใจผิด นี่เป็นเรื่องเกี่ยวกับการทำเครื่องหมายตัวแปรท้องถิ่นเป็น const ภายในบล็อกของรหัส (ที่เกิดขึ้นเป็นฟังก์ชัน) ฉันจะทำเช่นเดียวกันสำหรับตัวแปรท้องถิ่นใด ๆ มันเป็นฉากที่จะมี API ที่ const- ถูกต้องซึ่งเป็นสิ่งที่สำคัญจริงๆ
rlerallut

56
และมันสามารถดักจับข้อบกพร่องภายในฟังก์ชั่น - ถ้าคุณรู้ว่าไม่ควรเปลี่ยนพารามิเตอร์แล้วประกาศว่า const หมายความว่าคอมไพเลอร์จะบอกคุณว่าคุณตั้งใจแก้ไขหรือไม่
เอเดรีย

156

บางครั้ง (บ่อยเกินไป!) ฉันต้องแก้ให้หายยุ่งรหัส C ++ ของคนอื่น และเราทุกคนรู้ว่ารหัส C ++ ของคนอื่นเป็นระเบียบที่เกือบสมบูรณ์โดยนิยาม :) ดังนั้นสิ่งแรกที่ฉันทำเพื่อถอดรหัสการไหลของข้อมูลในเครื่องจะใส่constในทุกการกำหนดตัวแปรจนกว่าคอมไพเลอร์เริ่มเห่า นี่หมายถึงการโต้แย้งค่าที่มีคุณสมบัติคงที่เช่นกันเพราะมันเป็นเพียงตัวแปรในท้องถิ่นที่เริ่มต้นโดยผู้เรียก

โอ้ฉันหวังว่าตัวแปรเป็นconstโดยค่าเริ่มต้นและไม่แน่นอนที่ถูกต้องสำหรับตัวแปรที่ไม่ใช่ const :)


4
"ฉันต้องการตัวแปรที่เป็นค่าเริ่มต้นโดยรวม" - oxymoron ?? 8-) ทุกอย่างที่ "consting" ช่วยคุณแก้รหัสให้หายยุ่งได้อย่างไร หากผู้เขียนต้นฉบับเปลี่ยนอาร์กิวเมนต์คงที่ที่คาดคะเนคุณจะรู้ได้อย่างไรว่า var นั้นควรจะเป็นค่าคงที่ ยิ่งไปกว่านั้นตัวแปรส่วนใหญ่ (ไม่ใช่อาร์กิวเมนต์) นั้นหมายถึง ... ตัวแปร ดังนั้นคอมไพเลอร์ควรจะแตกในไม่ช้าหลังจากที่คุณเริ่มต้นกระบวนการไม่?
ysap

8
@ysap, 1. ทำเครื่องหมาย const ให้มากที่สุดเท่าที่จะทำได้, ให้ฉันดูว่าส่วนไหนกำลังขยับอยู่และไม่ได้ จากประสบการณ์ของฉันชาวบ้านจำนวนมากเป็นพฤตินัย const ไม่ใช่วิธีอื่น ๆ 2. "ตัวแปร Const" / "ตัวแปรที่ไม่เปลี่ยนรูป" อาจฟังดูแตกต่างไปจาก oxymoron แต่เป็นแบบฝึกหัดมาตรฐานในภาษาที่ใช้งานได้และบางส่วนที่ไม่สามารถใช้งานได้ ดูตัวอย่างสนิม: doc.rust-lang.org/book/variable-bindings.html
Constantin

1
ตอนนี้ยังมีมาตรฐานในบางสถานการณ์ใน c ++; ตัวอย่างแลมบ์ดา[x](){return ++x;}เป็นข้อผิดพลาด ดูที่นี่
Anatolyg

10
ตัวแปรคือ " const" โดยค่าเริ่มต้นในRust :)
phoenix

ตัวแปรไม่จำเป็นต้องเป็นค่าที่อนุญาตให้เปลี่ยนแปลงได้ ค่าที่เตรียมใช้งานด้วยสามารถแตกต่างกันในขณะใช้งานจริง
Sphynx

80

สองบรรทัดต่อไปนี้เทียบเท่ากับหน้าที่:

int foo (int a);
int foo (const int a);

เห็นได้ชัดว่าคุณจะไม่สามารถแก้ไขaในเนื้อหาของfooถ้ามันถูกกำหนดวิธีที่สอง แต่ไม่มีความแตกต่างจากภายนอก

ที่constมีประโยชน์จริง ๆ คือมีการอ้างอิงหรือพารามิเตอร์ตัวชี้:

int foo (const BigStruct &a);
int foo (const BigStruct *a);

สิ่งนี้กล่าวได้ว่า foo สามารถรับพารามิเตอร์ขนาดใหญ่ได้บางทีโครงสร้างข้อมูลที่มีขนาดกิกะไบต์โดยไม่ต้องคัดลอก นอกจากนี้ยังมีข้อความแจ้งผู้โทรว่า "ฟูจะไม่เปลี่ยนเนื้อหาของพารามิเตอร์นั้น" ผ่านการอ้างอิง const ยังช่วยให้คอมไพเลอร์ในการตัดสินใจประสิทธิภาพบางอย่าง

*: ยกเว้นว่ามันจะขับไล่กลุ่มเพื่อน แต่นั่นเป็นเรื่องอื่น


3
นั่นไม่ใช่สิ่งที่คำถามนี้เกี่ยวกับ; แน่นอนสำหรับการอ้างอิงหรือการขัดแย้งชี้ไปที่มันเป็นความคิดที่ดีที่จะใช้ const (ถ้าค่าอ้างอิงหรือชี้ไปที่ไม่ได้แก้ไข) โปรดทราบว่ามันไม่ใช่พารามิเตอร์ที่เป็น const ในตัวอย่างตัวชี้ของคุณ มันคือสิ่งที่พารามิเตอร์ชี้ไปที่นั่นคือ
tml

> การส่งผ่านการอ้างอิง const ยังทำให้คอมไพเลอร์สามารถตัดสินใจประสิทธิภาพบางอย่างได้ การเข้าใจผิดแบบคลาสสิก - คอมไพเลอร์ต้องพิจารณาตัวเองว่า const-ness คำหลัก const ไม่ได้ช่วยด้วยขอบคุณ aliasing ของตัวชี้และ const_cast
jheriko

73

const ฟุ่มเฟือยพิเศษไม่ดีจากจุดยืน API:

ใส่ const ฟุ่มเฟือยพิเศษในรหัสของคุณสำหรับพารามิเตอร์ประเภทที่แท้จริงที่ส่งผ่านโดยค่าจะตัดAPI ของคุณในขณะที่ไม่มีสัญญาที่มีความหมายต่อผู้โทรหรือผู้ใช้ API

มี 'const' มากเกินไปใน API เมื่อไม่ต้องการก็เหมือน " crying wolf " ในที่สุดผู้คนก็จะไม่สนใจ 'const' เพราะมันอยู่ทั่วทุกที่และไม่มีความหมายอะไรเลย

อาร์กิวเมนต์ "reductio ad absurdum" สำหรับ const พิเศษใน API นั้นดีสำหรับสองจุดแรกนี้คือถ้าพารามิเตอร์ const มากกว่าดีแล้วทุกอาร์กิวเมนต์ที่สามารถมี const ได้นั้นควรมี const อยู่ ในความเป็นจริงถ้าดีจริง ๆ คุณต้องการให้ const เป็นค่าเริ่มต้นสำหรับพารามิเตอร์และมีคำหลักเช่น "ไม่แน่นอน" เฉพาะเมื่อคุณต้องการเปลี่ยนพารามิเตอร์

ดังนั้นให้ลองใส่ const ในทุกที่ที่เราทำได้:

void mungerum(char * buffer, const char * mask, int count);

void mungerum(char * const buffer, const char * const mask, const int count);

พิจารณาบรรทัดของรหัสข้างต้น ไม่เพียง แต่การประกาศจะมีความยุ่งเหยิงมากขึ้นและอ่านได้นานขึ้นและยากขึ้นเท่านั้น แต่ผู้ใช้ API สามารถละเว้นคำหลักสามในสี่ทั้งหมดได้อย่างปลอดภัย อย่างไรก็ตามการใช้งานพิเศษของ 'const' ทำให้บรรทัดที่สองอาจเป็นอันตรายได้!

ทำไม?

ความผิดพลาดอย่างรวดเร็วของพารามิเตอร์แรกchar * const bufferอาจทำให้คุณคิดว่ามันจะไม่แก้ไขหน่วยความจำในบัฟเฟอร์ข้อมูลที่ส่งผ่าน - อย่างไรก็ตามสิ่งนี้ไม่เป็นความจริง!'const' ที่ไม่จำเป็นอาจนำไปสู่ข้อสันนิษฐานที่อันตรายและไม่ถูกต้องเกี่ยวกับ API ของคุณเมื่อสแกนหรืออ่านผิดอย่างรวดเร็ว


const ฟุ่มเฟือยก็ไม่ดีจากจุดยืนการติดตั้งโค้ดด้วยเช่นกัน:

#if FLEXIBLE_IMPLEMENTATION
       #define SUPERFLUOUS_CONST
#else
       #define SUPERFLUOUS_CONST             const
#endif

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count);

หาก FLEXIBLE_IMPLEMENTATION ไม่เป็นความจริงแสดงว่า API นั้น“ มีแนวโน้ม” ที่จะไม่ใช้ฟังก์ชันในวิธีแรกด้านล่าง

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       // Will break if !FLEXIBLE_IMPLEMENTATION
       while(count--)
       {
              *dest++=*source++;
       }
}

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       for(int i=0;i<count;i++)
       {
              dest[i]=source[i];
       }
}

นั่นเป็นสัญญาที่โง่มากที่จะทำ ทำไมคุณควรทำสัญญาที่ไม่ให้ประโยชน์ใด ๆ แก่ผู้โทรและ จำกัด การใช้งานของคุณเท่านั้น?

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

ยิ่งกว่านั้นมันเป็นสัญญาที่ตื้นเขินมากซึ่งง่าย (และหลีกเลี่ยงกฎหมาย)

inline void bytecopyWrapped(char * dest,
   const char *source, int count)
{
       while(count--)
       {
              *dest++=*source++;
       }
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source,SUPERFLUOUS_CONST int count)
{
    bytecopyWrapped(dest, source, count);
}

ดูฉันใช้มันด้วยวิธีใด ๆ แม้ว่าฉันสัญญาว่าจะไม่ - เพียงแค่ใช้ฟังก์ชั่นเสื้อคลุม มันเหมือนเมื่อคนเลวสัญญาว่าจะไม่ฆ่าคนในภาพยนตร์และสั่งให้ผู้ติดตามของเขาฆ่าพวกเขาแทน

กลุ่มของฟุ่มเฟือยเหล่านั้นมีค่าไม่เกินสัญญาจากภาพยนตร์คนเลว


แต่ความสามารถในการโกหกยิ่งแย่ลงไปอีก:

ฉันรู้แจ้งแล้วว่าคุณไม่สามารถจับ const ในส่วนหัว (ประกาศ) และรหัส (definition) โดยใช้ const ปลอม ผู้ให้การสนับสนุนที่มีความสุขอ้างว่าสิ่งนี้เป็นสิ่งที่ดีเพราะมันช่วยให้คุณใส่ความหมายได้เฉพาะในคำจำกัดความ

// Example of const only in definition, not declaration
class foo { void test(int *pi); };
void foo::test(int * const pi) { }

อย่างไรก็ตามการสนทนานั้นเป็นจริง ... คุณสามารถใส่กลุ่มปลอมในการประกาศและไม่สนใจมันในคำจำกัดความ สิ่งนี้ทำให้ const ที่ฟุ่มเฟือยใน API ยิ่งน่ากลัวยิ่งขึ้นและเป็นเรื่องโกหกที่น่ากลัว - ดูตัวอย่างนี้:

class foo
{
    void test(int * const pi);
};

void foo::test(int *pi) // Look, the const in the definition is so superfluous I can ignore it here
{
    pi++;  // I promised in my definition I wouldn't modify this
}

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

ดูตัวอย่างนี้ ข้อใดอ่านได้มากกว่า เห็นได้ชัดว่าเหตุผลเดียวที่ทำให้ตัวแปรพิเศษในฟังก์ชั่นที่สองเป็นเพราะนักออกแบบ API บางคนขว้าง const ที่ฟุ่มเฟือย?

struct llist
{
    llist * next;
};

void walkllist(llist *plist)
{
    llist *pnext;
    while(plist)
    {
        pnext=plist->next;
        walk(plist);
        plist=pnext;    // This line wouldn't compile if plist was const
    }
}

void walkllist(llist * SUPERFLUOUS_CONST plist)
{
    llist * pnotconst=plist;
    llist *pnext;
    while(pnotconst)
    {
        pnext=pnotconst->next;
        walk(pnotconst);
        pnotconst=pnext;
    }
}

หวังว่าเราจะได้เรียนรู้บางสิ่งที่นี่ const ฟุ่มเฟือยเป็นสิ่งที่อุจาดนัยน์ตา API, รก, น่ารำคาญ, สัญญาตื้นและไม่มีความหมาย, อุปสรรคที่ไม่จำเป็นและบางครั้งนำไปสู่ข้อผิดพลาดที่อันตรายมาก


9
ทำไมต้องลงคะแนน? มันมีประโยชน์มากขึ้นถ้าคุณแสดงความคิดเห็นสั้น ๆ เกี่ยวกับ downvote
Adisak

7
จุดรวมของการใช้อาร์กิวเมนต์ const คือการทำให้บรรทัดที่ทำเครื่องหมายล้มเหลว (plist = pnext) มันเป็นมาตรการความปลอดภัยที่เหมาะสมเพื่อให้การโต้แย้งฟังก์ชั่นไม่เปลี่ยนรูป ฉันเห็นด้วยกับจุดของคุณว่าพวกเขาไม่ดีในการประกาศฟังก์ชั่น (เพราะพวกเขามีความฟุ่มเฟือย) แต่พวกเขาสามารถตอบสนองวัตถุประสงค์ของพวกเขาในบล็อกการใช้งาน
touko

23
@Adisak ฉันไม่เห็นอะไรผิดปกติกับคำตอบของคุณ แต่ดูเหมือนว่าจากความคิดเห็นของคุณที่คุณพลาดจุดสำคัญไป ความหมายของการทำงาน / การดำเนินงานคือไม่ได้เป็นส่วนหนึ่งของ API ซึ่งเป็นเพียงฟังก์ชั่นการประกาศ อย่างที่คุณได้กล่าวไว้การประกาศฟังก์ชั่นด้วยพารามิเตอร์ const นั้นไม่มีจุดหมายและเพิ่มความยุ่งเหยิง อย่างไรก็ตามผู้ใช้ API อาจไม่จำเป็นต้องเห็นการใช้งาน ในขณะที่ผู้ดำเนินการอาจตัดสินใจที่จะคัดเลือกคุณสมบัติบางส่วนของพารามิเตอร์ในการกำหนดฟังก์ชั่นเท่านั้นเพื่อความชัดเจนซึ่งเป็นสิ่งที่ดีอย่างสมบูรณ์
jw013

17
@ jw013 ถูกต้องvoid foo(int)และvoid foo(const int)เป็นฟังก์ชั่นเดียวกันแน่นอนไม่เกินพิกัด ideone.com/npN4W4 ideone.com/tZav9R const ที่นี่เป็นเพียงรายละเอียดการใช้งานของร่างกายฟังก์ชั่นและไม่มีผลต่อการแก้ปัญหาการโอเวอร์โหลด ปล่อย const ออกจากการประกาศสำหรับ API ที่ปลอดภัยและ neater แต่ใส่ const เข้าไปในคำนิยามหากคุณตั้งใจจะไม่แก้ไขค่าที่คัดลอก
Oktalist

3
@Adisak ฉันรู้ว่านี่เก่า แต่ฉันเชื่อว่าการใช้งานที่ถูกต้องสำหรับ API สาธารณะจะเป็นวิธีอื่น ด้วยวิธีนี้นักพัฒนาที่ทำงานกับ internals จะไม่ทำผิดพลาดเช่นpi++เมื่อไม่ควรทำ
CoffeeandCode

39

const ควรเป็นค่าเริ่มต้นใน C ++ แบบนี้ :

int i = 5 ; // i is a constant

var int i = 5 ; // i is a real variable

8
ความคิดของฉัน
Constantin

24
ความเข้ากันได้กับ C นั้นสำคัญมากอย่างน้อยสำหรับคนที่ออกแบบ C ++ เพื่อพิจารณาสิ่งนี้

4
น่าสนใจฉันไม่เคยคิดเลย
ด่าน

6
ในทำนองเดียวกันunsignedควรเป็นค่าเริ่มต้นใน C ++ เช่นนี้และint i = 5; // i is unsigned signed int i = 5; // i is signed
hkBattousai

25

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

foo() = 42

เมื่อคุณหมายถึง:

foo() == 42

หาก foo () ถูกกำหนดไว้เพื่อส่งคืนการอ้างอิงที่ไม่ใช่ const:

int& foo() { /* ... */ }

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

const int& foo() { /* ... */ }

กำจัดความเป็นไปได้นี้


6
คอมไพเลอร์ตัวใดทำงานอย่างไร GCC ให้ข้อผิดพลาดขณะพยายามคอมไพล์foo() = 42: ข้อผิดพลาด: lvalue จำเป็นต้องเป็น operand ด้านซ้ายของการมอบหมาย
gavrie

นี่มันไม่ถูกต้อง foo () = 42 เหมือนกับ 2 = 3 นั่นคือข้อผิดพลาดของคอมไพเลอร์ และการคืนค่า const นั้นไม่มีจุดหมายอย่างสมบูรณ์ มันไม่ได้ทำอะไรเลยสำหรับประเภทในตัว
Josh

2
ฉันได้พบกับการใช้งานของ const และฉันสามารถบอกคุณได้ในท้ายที่สุดมันสร้างความยุ่งยากมากกว่าประโยชน์ คำแนะนำ: const int foo()เป็นประเภทที่แตกต่างจากint foo()ที่นำคุณเข้าสู่ปัญหาใหญ่หากคุณใช้สิ่งต่าง ๆ เช่นพอยน์เตอร์ของฟังก์ชั่นระบบสัญญาณ / สล็อตหรือเพิ่ม :: ผูก
Mephane

2
ฉันแก้ไขรหัสเพื่อรวมค่าส่งคืนการอ้างอิงแล้ว
Avdi

ไม่const int& foo()เหมือนกันอย่างมีประสิทธิภาพint foo()เนื่องจากการปรับค่าส่งคืนให้เหมาะสมหรือไม่
Zantier

15

มีการสนทนาที่ดีเกี่ยวกับหัวข้อนี้ในเก่า "คุรุของสัปดาห์" บทความเกี่ยวกับ comp.lang.c ++ เป็น. ชะลอลงที่นี่ที่นี่

บทความ GOTW ที่สอดคล้องกันคืออยู่บนเว็บไซต์ Sutter สมุนไพรของที่นี่


1
Herb Sutter เป็นคนที่ฉลาดจริงๆ :-) คุ้มค่าที่จะอ่านและฉันเห็นด้วยกับทุกประเด็นของเขา
Adisak

2
บทความที่ดี แต่ฉันไม่เห็นด้วยกับเขาเกี่ยวกับข้อโต้แย้ง ฉันทำให้พวกเขาเป็นคนดีเพราะพวกเขาเป็นเหมือนตัวแปรและฉันไม่ต้องการให้ใครทำการเปลี่ยนแปลงใด ๆ กับข้อโต้แย้งของฉัน
QBziZ

9

ฉันพูด const พารามิเตอร์ค่าของคุณ

พิจารณาฟังก์ชั่นบั๊กกี้นี้:

bool isZero(int number)
{
  if (number = 0)  // whoops, should be number == 0
    return true;
  else
    return false;
}

หากพารามิเตอร์ number เป็น const คอมไพเลอร์จะหยุดและเตือนให้เราทราบถึงข้อบกพร่อง


2
วิธีอื่นใช้ถ้า (0 == หมายเลข) ... อื่น ... ;
Johannes Schaub - litb

5
@ ChrisHuang-Leaver ไม่น่ากลัวถ้าพูดถึง Yoda คุณ: stackoverflow.com/a/2430307/210916
MPelletier

GCC / เสียงดังกังวาน - จะให้ - วงเล็บซึ่งต้องการให้คุณ "ถ้า (((จำนวน = 0))" ถ้านั่นเป็นสิ่งที่คุณตั้งใจจะทำ ซึ่งใช้งานได้ดีแทน Yoda
Jetski S-type

8

ฉันใช้ const ในพารามิเตอร์ฟังก์ชันที่อ้างอิง (หรือตัวชี้) ซึ่งเป็นข้อมูล [เท่านั้น] และจะไม่ถูกแก้ไขโดยฟังก์ชัน ความหมายเมื่อวัตถุประสงค์ของการใช้การอ้างอิงคือเพื่อหลีกเลี่ยงการคัดลอกข้อมูลและไม่อนุญาตให้เปลี่ยนพารามิเตอร์ที่ส่งผ่าน

การวาง const ในพารามิเตอร์บูลีน b ในตัวอย่างของคุณจะทำให้มีข้อ จำกัด ในการนำไปใช้เท่านั้นและไม่สนับสนุนอินเทอร์เฟซของคลาส (แม้ว่าจะไม่แนะนำให้เปลี่ยนพารามิเตอร์)

ฟังก์ชั่นลายเซ็นสำหรับ

void foo(int a);

และ

void foo(const int a);

เหมือนกันซึ่งอธิบาย. c และ. h ของคุณ

Asaf


6

หากคุณใช้->*หรือ.*โอเปอเรเตอร์จะต้องมี

มันทำให้คุณไม่สามารถเขียนบางอย่างเช่น

void foo(Bar *p) { if (++p->*member > 0) { ... } }

ซึ่งฉันเกือบทำในตอนนี้และซึ่งอาจไม่ทำสิ่งที่คุณตั้งใจ

สิ่งที่ฉันตั้งใจจะพูดคือ

void foo(Bar *p) { if (++(p->*member) > 0) { ... } }

และถ้าฉันใส่constในระหว่างBar *และpคอมไพเลอร์จะได้บอกฉันว่า


4
ฉันจะตรวจสอบการอ้างอิงตามลำดับความสำคัญของโอเปอเรเตอร์ทันทีเมื่อฉันกำลังผสมกันว่าโอเปอเรเตอร์จำนวนมาก (ถ้าฉันยังไม่รู้ 100%) ดังนั้น IMO จึงไม่ใช่ปัญหา
mk12

5

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


5

const ไม่มีจุดหมายเมื่ออาร์กิวเมนต์ถูกส่งผ่านตามค่าเนื่องจากคุณจะไม่แก้ไขวัตถุของผู้โทร

const ควรเป็นที่นิยมเมื่อผ่านการอ้างอิงเว้นแต่วัตถุประสงค์ของฟังก์ชั่นคือการปรับเปลี่ยนค่าที่ผ่าน

ในที่สุดฟังก์ชั่นที่ไม่ได้แก้ไขวัตถุปัจจุบัน (นี้) สามารถและอาจจะมีการประกาศ const ตัวอย่างด้านล่าง:

int SomeClass::GetValue() const {return m_internalValue;}

นี่คือสัญญาที่จะไม่แก้ไขวัตถุที่ใช้การโทรนี้ กล่าวอีกนัยหนึ่งคุณสามารถโทร:

const SomeClass* pSomeClass;
pSomeClass->GetValue();

หากฟังก์ชั่นไม่ได้เป็น const จะส่งผลให้เกิดคำเตือนของคอมไพเลอร์


5

การทำเครื่องหมายพารามิเตอร์ค่า 'const' เป็นเรื่องที่แน่นอน

อย่างไรก็ตามจริง ๆ แล้วฉันชอบทำเครื่องหมายพารามิเตอร์ค่า const เช่นเดียวกับในตัวอย่างของคุณ

void func(const int n, const long l) { /* ... */ }

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

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

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

ฉันมั่นใจได้ว่าถ้าฉันทำการคำนวณด้วย 'n' และ 'l' ฉันสามารถ refactor / ย้ายการคำนวณนั้นโดยไม่ต้องกลัวว่าจะได้ผลลัพธ์ที่แตกต่างเพราะฉันพลาดสถานที่ที่มีการเปลี่ยนแปลงอย่างใดอย่างหนึ่งหรือทั้งสองอย่าง

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


4

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


3

ฉันมักจะใช้ const ทุกที่ที่เป็นไปได้ (หรือคำหลักที่เหมาะสมอื่น ๆ สำหรับภาษาเป้าหมาย) ฉันทำสิ่งนี้อย่างหมดจดเพราะมันอนุญาตให้คอมไพเลอร์ทำการเพิ่มประสิทธิภาพพิเศษที่ไม่สามารถทำได้เป็นอย่างอื่น เนื่องจากฉันไม่รู้ว่าการเพิ่มประสิทธิภาพเหล่านี้อาจเป็นอะไรฉันจึงทำเสมอแม้ว่ามันจะดูไร้สาระก็ตาม

สำหรับสิ่งที่ฉันรู้คอมไพเลอร์อาจเห็นพารามิเตอร์ค่า const และพูดว่า "เฮ้ฟังก์ชั่นนี้ไม่ได้ทำการแก้ไขมันดังนั้นฉันสามารถผ่านการอ้างอิงและบันทึกวงจรนาฬิกาได้" ฉันไม่คิดว่ามันจะทำสิ่งนี้เพราะมันเปลี่ยนลายเซ็นของฟังก์ชั่น แต่มันทำให้ประเด็น บางทีมันอาจจะเป็นกองซ้อนที่แตกต่างกันหรือบางอย่าง ... ประเด็นก็คือฉันไม่รู้ แต่ฉันรู้ว่าการพยายามฉลาดกว่าคอมไพเลอร์จะทำให้ฉันรู้สึกอับอาย

C ++ มีสัมภาระเพิ่มเติมบางส่วนโดยมีแนวคิดเรื่องความถูกต้องของ const ดังนั้นมันจึงมีความสำคัญมากยิ่งขึ้น


ในขณะที่มันอาจช่วยในบางกรณีฉันสงสัยว่าความเป็นไปได้ของการส่งเสริม optimisations จะคุยโวเป็นประโยชน์constอย่างมาก ค่อนข้างเป็นเรื่องของเจตนาที่ระบุไว้ในการนำไปปฏิบัติและจับนักคิดในภายหลัง (โดยไม่ได้ตั้งใจจะเพิ่มตัวแปรท้องถิ่นที่ผิดเพราะมันไม่ได้const) ในแบบคู่ขนานฉันยังเพิ่มว่าคอมไพเลอร์ยินดีต้อนรับอย่างมากที่จะเปลี่ยนลายเซ็นของฟังก์ชันในแง่ที่ว่าฟังก์ชันสามารถถูกแทรกและเมื่อมีการเปลี่ยนแปลงวิธีทั้งหมดที่พวกเขาทำงาน การเพิ่มหรือลบการอ้างอิงการสร้าง 'ตัวแปร' ตัวอักษร ฯลฯ ล้วนอยู่ในกฎ as-if
underscore_d

3

1. คำตอบที่ดีที่สุดจากการประเมินของฉัน:

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

2. คำพูดของฉัน (เห็นด้วยกับคำตอบที่ดีที่สุด):

  1. สำหรับการผ่านมูลค่าไม่มีประโยชน์ที่จะเพิ่ม constสำหรับการส่งผ่านโดยค่ามีประโยชน์ต่อการเพิ่มไม่มีทั้งหมดนี้คือ:
    1. จำกัด ตัวดำเนินการที่จะต้องทำสำเนาทุกครั้งที่พวกเขาต้องการเปลี่ยนพารามิเตอร์อินพุตในซอร์สโค้ด (ซึ่งการเปลี่ยนแปลงนั้นจะไม่มีผลข้างเคียงเนื่องจากสิ่งที่ส่งผ่านนั้นเป็นสำเนาอยู่แล้วเนื่องจากมันผ่านตามตัวอักษร) และบ่อยครั้งที่การเปลี่ยนพารามิเตอร์อินพุตที่ส่งผ่านโดยค่าถูกนำมาใช้ในการใช้ฟังก์ชั่นดังนั้นการเพิ่มconstทุกที่สามารถขัดขวางสิ่งนี้ได้
    2. และเพิ่มconstรหัสที่ไม่จำเป็นในconstทุกที่ดึงความสนใจออกไปจากconsts ที่จำเป็นอย่างแท้จริงที่จะต้องมีรหัสที่ปลอดภัย
  2. เมื่อจัดการกับตัวชี้หรืออ้างอิงแต่constเป็นวิกฤตที่สำคัญเมื่อมีความจำเป็นและจะต้องถูกนำมาใช้เป็นจะป้องกันไม่ให้เกิดผลข้างเคียงที่ไม่พึงประสงค์กับการเปลี่ยนแปลงถาวรนอกฟังก์ชั่นและดังนั้นทุกตัวชี้เดียวหรือการอ้างอิงจะต้องใช้constเมื่อ parm คือการป้อนข้อมูลเท่านั้น ไม่ใช่เอาต์พุต การใช้const เฉพาะกับพารามิเตอร์ที่ส่งผ่านโดยการอ้างอิงหรือตัวชี้มีประโยชน์เพิ่มเติมในการทำให้ชัดเจนจริง ๆ ว่าพารามิเตอร์ใดเป็นตัวชี้หรือการอ้างอิง มันเป็นอีกสิ่งหนึ่งที่ต้องโดดออกมาและพูดว่า "ระวัง! สิ่งที่constอยู่ติดกับมันคือการอ้างอิงหรือตัวชี้!"
  3. สิ่งที่ฉันได้อธิบายไว้ข้างต้นนั้นเป็นฉันทามติที่พบบ่อยในองค์กรซอฟต์แวร์มืออาชีพที่ฉันเคยทำงานมาและได้รับการพิจารณาว่าเป็นแนวปฏิบัติที่ดีที่สุด บางครั้งแม้กระทั่งกฎก็เข้มงวด: "ไม่เคยใช้ const กับพารามิเตอร์ที่ส่งผ่านตามค่า แต่มักจะใช้กับพารามิเตอร์ที่ส่งผ่านโดยการอ้างอิงหรือตัวชี้ถ้าพวกเขาเป็นอินพุตเท่านั้น"

3. คำพูดของ Google (เห็นด้วยกับฉันและคำตอบที่ดีที่สุด):

(จาก " คู่มือสไตล์ Google C ++ ")

สำหรับพารามิเตอร์ฟังก์ชันที่ส่งผ่านตามค่า const จะไม่มีผลกับผู้เรียกดังนั้นไม่แนะนำในการประกาศฟังก์ชัน ดูTOTW #

การใช้ const กับตัวแปรท้องถิ่นไม่ได้รับการสนับสนุนหรือท้อแท้

ที่มา: ส่วน "การใช้ const" ของคู่มือ ++ สไตล์ Google C: https://google.github.io/styleguide/cppguide.html#Use_of_const นี่เป็นส่วนที่มีค่าจริงๆดังนั้นโปรดอ่านทั้งหมวด

โปรดทราบว่า "TotW # 109" หมายถึง"เคล็ดลับประจำสัปดาห์ # 109: ความหมายconstในการประกาศฟังก์ชัน"และยังเป็นประโยชน์ในการอ่าน มันมีข้อมูลมากขึ้นและกำหนดน้อยลงเกี่ยวกับสิ่งที่ต้องทำและตามบริบทมาก่อนกฎของคู่มือสไตล์ C ++ ของ Google ที่constยกมาข้างต้น แต่จากความชัดเจนที่ได้ให้ไว้constกฎที่ยกมานั้นถูกเพิ่มเข้าไปใน Google C ++ คู่มือสไตล์

นอกจากนี้โปรดทราบว่าแม้ว่าฉันจะอ้างถึงคู่มือสไตล์ Google C ++ ที่นี่เพื่อป้องกันตำแหน่งของฉัน แต่ก็ไม่ได้หมายความว่าฉันจะทำตามคำแนะนำหรือแนะนำตามคู่มือเสมอ บางส่วนของสิ่งที่พวกเขาแนะนำเป็นเพียงธรรมดาแปลกเช่นพวกเขาkDaysInAWeekตั้งชื่อสไตล์สำหรับ "คงที่ชื่อ" อย่างไรก็ตามมันยังคงมีประโยชน์และเกี่ยวข้องกับการชี้ให้เห็นเมื่อ บริษัท ด้านเทคนิคและซอฟต์แวร์ที่ประสบความสำเร็จและมีอิทธิพลมากที่สุดแห่งหนึ่งของโลกใช้เหตุผลเดียวกับที่ฉันและคนอื่น ๆ เช่น @Adisak ทำเพื่อสำรองมุมมองของเราในเรื่องนี้

4. ส่วนหลังของเสียงดังกราวclang-tidy, มีตัวเลือกบางอย่างสำหรับสิ่งนี้:

A. นอกจากนี้ยังเป็นที่น่าสังเกตว่า linter เสียงดังกราวของclang-tidyได้ตัวเลือกreadability-avoid-const-params-in-decls, อธิบายไว้ที่นี่เพื่อสนับสนุนการบังคับใช้ในฐานรหัสไม่ได้ใช้constสำหรับพารามิเตอร์ฟังก์ชั่นการส่งผ่านโดยค่า :

ตรวจสอบว่าการประกาศฟังก์ชั่นมีพารามิเตอร์ที่ const ระดับสูงสุด

ค่า const ในการประกาศไม่ส่งผลกระทบต่อลายเซ็นของฟังก์ชั่นดังนั้นจึงไม่ควรใส่

ตัวอย่าง:

void f(const string);   // Bad: const is top level.
void f(const string&);  // Good: const is not top level.

และนี่คืออีกสองตัวอย่างฉันกำลังเพิ่มความสมบูรณ์และความชัดเจน:

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

B. นอกจากนี้ยังมีตัวเลือกนี้: readability-const-return-type- https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

5. แนวทางปฏิบัติของฉันเกี่ยวกับวิธีที่ฉันจะบอกแนวสไตล์ในเรื่องนี้:

ฉันแค่คัดลอกและวางลงในคู่มือสไตล์ของฉัน:

[เริ่มต้นคัดลอก / วาง]

  1. ใช้ constกับพารามิเตอร์ฟังก์ชันที่ส่งผ่านโดยการอ้างอิงหรือตัวชี้เมื่อเนื้อหาของพวกเขา (สิ่งที่พวกเขาชี้ไป) ตั้งใจที่จะไม่เปลี่ยนแปลง constวิธีนี้มันจะกลายเป็นที่เห็นได้ชัดเมื่อตัวแปรส่งผ่านโดยการอ้างอิงหรือชี้คาดว่าจะมีการเปลี่ยนแปลงเพราะมันจะขาด ในกรณีที่ใช้งานนี้constจะช่วยป้องกันผลข้างเคียงที่ไม่คาดคิดภายนอกฟังก์ชั่น
  2. มันเป็นเรื่องที่ไม่แนะนำให้ใช้กับการใช้constพารามิเตอร์ที่ฟังก์ชั่นผ่านค่าเพราะconstไม่มีผลต่อการโทร: แม้ว่าตัวแปรที่มีการเปลี่ยนแปลงในการทำงานจะไม่มีผลข้างเคียงนอกฟังก์ชั่น ดูแหล่งข้อมูลต่อไปนี้สำหรับการให้เหตุผลและความเข้าใจเพิ่มเติม:
    1. "คู่มือสไตล์ Google C ++" "การใช้งาน const"
    2. "เคล็ดลับประจำสัปดาห์ # 109: มีความหมายconstในการประกาศฟังก์ชัน"
    3. คำตอบ Stack Overflow ของ Adisak เกี่ยวกับ "การใช้ 'const' สำหรับพารามิเตอร์ฟังก์ชัน"
  3. " อย่าใช้ระดับบนสุดconst[เช่น: constในพารามิเตอร์ที่ส่งผ่านโดยค่า ] กับพารามิเตอร์ฟังก์ชันในการประกาศที่ไม่ใช่คำจำกัดความ (และระวังอย่าคัดลอก / วางความหมายconst) มันไม่มีความหมายและไม่สนใจโดยคอมไพเลอร์ และอาจทำให้ผู้อ่านเข้าใจผิด "( https://abseil.io/tips/109เน้นเพิ่ม)
    1. constqualifier เท่านั้นที่มีผลต่อการรวบรวมคือสิ่งที่อยู่ในนิยามฟังก์ชันไม่ใช่ตัวประกาศในการประกาศไปข้างหน้าของฟังก์ชันเช่นในการประกาศ function (method) ในไฟล์ส่วนหัว
  4. อย่าใช้ระดับบนสุดconst[ie: constกับตัวแปรที่ส่งผ่านโดยค่า ] กับค่าที่ส่งคืนโดยฟังก์ชัน
  5. การใช้พconstอยน์เตอร์หรือการอ้างอิงที่ส่งคืนโดยฟังก์ชันนั้นขึ้นอยู่กับตัวดำเนินการเนื่องจากบางครั้งมันก็มีประโยชน์
  6. สิ่งที่ต้องทำ: บังคับใช้บางอย่างข้างต้นด้วยclang-tidyตัวเลือกต่อไปนี้:
    1. https://clang.llvm.org/extra/clang-tidy/checks/readability-avoid-const-params-in-decls.html
    2. https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

นี่คือตัวอย่างของรหัสเพื่อแสดงconstกฎที่อธิบายข้างต้น:

constตัวอย่างพารามิเตอร์:
(บางคนยืมมาจากที่นี่ )

void f(const std::string);   // Bad: const is top level.
void f(const std::string&);  // Good: const is not top level.

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

constตัวอย่างประเภทส่งคืน:
(บางคนยืมมาจากที่นี่ )

// BAD--do not do this:
const int foo();
const Clazz foo();
Clazz *const foo();

// OK--up to the implementer:
const int* foo();
const int& foo();
const Clazz* foo();

[คัดลอก / วางปลาย]


2

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

มันไม่ได้เป็นสิ่งที่ต้องทำโดยเฉพาะอย่างยิ่ง แต่ประโยชน์ที่ได้นั้นยอดเยี่ยมเพราะมันไม่ส่งผลกระทบต่อ API ของคุณและมันเพิ่มการพิมพ์ดังนั้นจึงมักจะไม่ทำ


2

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

สิ่งสำคัญจริงๆคือการทำเครื่องหมายวิธีเป็น const หากพวกเขาไม่ได้แก้ไขอินสแตนซ์ของพวกเขา ทำสิ่งนี้ตามที่คุณไปเพราะมิฉะนั้นคุณอาจจบด้วย const_cast <> จำนวนมากหรือคุณอาจพบว่าการทำเครื่องหมายวิธีการ const ต้องเปลี่ยนรหัสมากเพราะเรียกวิธีการอื่นที่ควรได้รับการทำเครื่องหมาย const

ฉันยังมีแนวโน้มที่จะทำเครื่องหมาย vars เฉพาะที่ถ้าฉันไม่ต้องการแก้ไข ฉันเชื่อว่ามันทำให้โค้ดเข้าใจง่ายขึ้นโดยทำให้ง่ายต่อการระบุ "ส่วนที่เคลื่อนไหว"



2

ฉันใช้ const ฉันก็ทำได้ Const สำหรับพารามิเตอร์หมายความว่าพวกเขาไม่ควรเปลี่ยนค่าของพวกเขา สิ่งนี้มีค่ามากเมื่อผ่านการอ้างอิง const สำหรับฟังก์ชั่นประกาศว่าฟังก์ชั่นไม่ควรเปลี่ยนสมาชิกชั้นเรียน


2

เพื่อสรุป:

  • "ปกติแล้วการส่งผ่านข้อมูลโดยใช้ค่าไม่เหมาะสมและทำให้เข้าใจผิดได้ดีที่สุด" ตั้งแต่GOTW006
  • แต่คุณสามารถเพิ่มพวกเขาใน. cpp เช่นเดียวกับที่คุณทำกับตัวแปร
  • โปรดทราบว่าไลบรารีมาตรฐานไม่ได้ใช้ const std::vector::at(size_type pos)เช่น สิ่งที่ดีพอสำหรับห้องสมุดมาตรฐานนั้นดีสำหรับฉัน

2
"มีอะไรดีพอสำหรับห้องสมุดมาตรฐานดีสำหรับฉัน" ไม่เป็นความจริงเสมอไป ตัวอย่างเช่นไลบรารีมาตรฐานใช้ชื่อตัวแปรที่น่าเกลียดเช่นนี้_Tmpตลอดเวลา - คุณไม่ต้องการสิ่งนี้ (จริงๆแล้วคุณไม่ได้รับอนุญาตให้ใช้)
anatolyg

1
@anatolyg นี่เป็นรายละเอียดการใช้งาน
Fernando Pelliccioni

2
ตกลงทั้งชื่อตัวแปรและชนิดที่ผ่านการรับรองในรายการอาร์กิวเมนต์เป็นรายละเอียดการนำไปปฏิบัติ สิ่งที่ฉันต้องการจะพูดคือการใช้ห้องสมุดมาตรฐานบางครั้งก็ไม่ดี บางครั้งคุณสามารถ (และควร) ทำได้ดีกว่า เมื่อไหร่รหัสสำหรับไลบรารีมาตรฐานที่เขียน - 10 ปีที่แล้ว 5 ปีที่แล้ว (ชิ้นส่วนใหม่ล่าสุดของมัน)? เราสามารถเขียนโค้ดที่ดีกว่าวันนี้
Anatolyg

1

หากพารามิเตอร์ถูกส่งผ่านตามค่า (และไม่ใช่การอ้างอิง) มักจะมีความแตกต่างไม่มากว่าพารามิเตอร์จะประกาศเป็น const หรือไม่ (เว้นแต่จะมีสมาชิกอ้างอิง - ไม่ใช่ปัญหาสำหรับประเภทในตัว) หากพารามิเตอร์เป็นการอ้างอิงหรือตัวชี้มันจะดีกว่าเพื่อปกป้องหน่วยความจำอ้างอิง / ชี้ไปที่ไม่ใช่ตัวชี้ (ฉันคิดว่าคุณไม่สามารถอ้างอิงตัวเองได้ const ไม่ใช่ว่ามันสำคัญมากเพราะคุณไม่สามารถเปลี่ยนผู้ตัดสินได้) . ดูเหมือนว่าเป็นความคิดที่ดีที่จะปกป้องทุกสิ่งที่คุณทำได้ คุณสามารถละเว้นได้โดยไม่ต้องกลัวว่าจะทำผิดพลาดหากพารามิเตอร์เป็นเพียง POD (รวมถึงประเภทในตัว) และไม่มีโอกาสที่พวกเขาจะเปลี่ยนไปตามถนน (เช่นในตัวอย่างพารามิเตอร์ bool)

ฉันไม่รู้เกี่ยวกับความแตกต่างในการประกาศไฟล์. h / .cpp แต่มันสมเหตุสมผลบ้าง ที่ระดับรหัสเครื่องไม่มีอะไรที่เป็น "const" ดังนั้นหากคุณประกาศฟังก์ชั่น (ใน. h) เป็น non-const รหัสจะเหมือนกับว่าคุณประกาศว่าเป็น const (ปรับให้เหมาะสม) อย่างไรก็ตามมันช่วยให้คุณสมัครคอมไพเลอร์ที่คุณจะไม่เปลี่ยนค่าของตัวแปรภายในการใช้งานของฟังก์ชั่น (.ccp) อาจเป็นประโยชน์ในกรณีที่คุณรับช่วงต่อจากส่วนต่อประสานที่อนุญาตการเปลี่ยนแปลง แต่คุณไม่จำเป็นต้องเปลี่ยนเป็นพารามิเตอร์เพื่อให้สามารถใช้งานได้ตามที่ต้องการ


0

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


4
บางครั้งคุณต้องการส่งวัตถุโดยอ้างอิง (ด้วยเหตุผลด้านประสิทธิภาพ) แต่ไม่เปลี่ยนวัตถุดังนั้น const จึงเป็นสิ่งจำเป็น การรักษาพารามิเตอร์เหล่านี้ทั้งหมด - แม้กระทั่ง bools - const จะเป็นการฝึกฝนที่ดีทำให้โค้ดของคุณอ่านง่ายขึ้น
gbjbaanb

0

สิ่งที่ต้องจดจำด้วย const คือมันง่ายกว่ามากในการทำสิ่งต่าง ๆ ให้คงที่ตั้งแต่เริ่มต้นมากกว่าที่จะลองและวางไว้ในภายหลัง

ใช้ const เมื่อคุณต้องการบางสิ่งบางอย่างที่จะไม่เปลี่ยนแปลง - มันเป็นคำใบ้เพิ่มเติมที่อธิบายถึงสิ่งที่ฟังก์ชั่นของคุณและสิ่งที่คาดหวัง ฉันเห็น C API หลายอย่างที่สามารถทำได้กับบางคนโดยเฉพาะอย่างยิ่งที่ยอมรับ c-strings!

ฉันอยากจะละเว้นคำหลัก const ในไฟล์ cpp มากกว่าส่วนหัว แต่เนื่องจากฉันมีแนวโน้มที่จะตัด + วางพวกเขาพวกเขาจะถูกเก็บไว้ในทั้งสองแห่ง ฉันไม่รู้ว่าทำไมคอมไพเลอร์ยอมให้ฉันเดาว่ามันเป็นคอมไพเลอร์ แนวทางปฏิบัติที่ดีที่สุดคือการใส่คำหลัก const ของคุณไว้ในทั้งสองไฟล์


ฉันไม่ได้รับสิ่งนี้เลย ทำไมคุณถึงอยากจะละเว้นมันในไฟล์ cpp (นิยามฟังก์ชั่น) นั่นคือสิ่งที่จริงหมายถึงบางสิ่งบางอย่างและสามารถจับข้อผิดพลาด ทำไมคุณถึงคิดว่าเป็นวิธีที่ดีที่สุดที่จะวางสายไว้ในที่ทั้งสอง ในไฟล์ส่วนหัว (การประกาศฟังก์ชั่น) มันไม่มีความหมายอะไรและปิดกั้น API อาจมีค่าเล็ก ๆ น้อย ๆ ที่มีการประกาศและป้องกันเหมือนกัน แต่สำหรับฉันแล้วมันเป็นประโยชน์เล็กน้อยจริง ๆ เมื่อเทียบกับปัญหาการถ่วง API
Don Hatch

@ DonHatch 8 ปีต่อมาว้าว อย่างไรก็ตามในขณะที่ OP กล่าวว่า "ฉันรู้สึกประหลาดใจที่ได้เรียนรู้ว่าคุณสามารถละเว้น const จากพารามิเตอร์ในการประกาศฟังก์ชัน แต่สามารถรวมไว้ในนิยามฟังก์ชันได้"
gbjbaanb

0

ไม่มีเหตุผลที่จะสร้างพารามิเตอร์ค่า "const" เนื่องจากฟังก์ชันสามารถแก้ไขสำเนาของตัวแปรได้เท่านั้น

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


0

พารามิเตอร์ Const มีประโยชน์เฉพาะเมื่อพารามิเตอร์ถูกส่งผ่านโดยการอ้างอิงเช่นการอ้างอิงหรือตัวชี้ เมื่อคอมไพเลอร์เห็นพารามิเตอร์ const ตรวจสอบให้แน่ใจว่าตัวแปรที่ใช้ในพารามิเตอร์ไม่ได้ถูกแก้ไขภายในเนื้อความของฟังก์ชัน ทำไมทุกคนต้องการสร้างพารามิเตอร์ตามค่าคงที่ :-)


ด้วยเหตุผลหลายประการ การกำหนดพารามิเตอร์ตามค่าconstอย่างชัดเจน: 'ฉันไม่จำเป็นต้องแก้ไขสิ่งนี้ดังนั้นฉันจึงประกาศว่า หากฉันพยายามแก้ไขในภายหลังให้ฉันรวบรวมข้อผิดพลาดครั้งเพื่อให้ฉันสามารถแก้ไขข้อผิดพลาดหรือยกเลิกการทำเครื่องหมายเป็นconst' ดังนั้นจึงเป็นเรื่องของสุขลักษณะและความปลอดภัย สำหรับทุกสิ่งที่เพิ่มลงในไฟล์การใช้งานมันควรจะเป็นสิ่งที่ผู้คนทำในฐานะ IMO ล้วนๆ
underscore_d

0

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


0

consts ทั้งหมดในตัวอย่างของคุณไม่มีวัตถุประสงค์ C ++ เป็นค่าแบบ pass-by-value ตามค่าเริ่มต้นดังนั้นฟังก์ชันจะได้รับสำเนาของ ints และ booleans แม้ว่าฟังก์ชั่นจะปรับเปลี่ยน แต่สำเนาของผู้โทรจะไม่ได้รับผลกระทบ

ดังนั้นฉันจะหลีกเลี่ยง consts พิเศษเพราะ

  • พวกเขากำลัง redudant
  • พวกเขาถ่วงข้อความ
  • พวกเขาป้องกันฉันจากการเปลี่ยนค่าการส่งผ่านในกรณีที่อาจมีประโยชน์หรือมีประสิทธิภาพ

-1

ฉันรู้ว่าคำถามคือ "นิดหน่อย" ล้าสมัย แต่เมื่อฉันเข้ามาคนอื่นอาจทำเช่นนั้นในอนาคต ... ... ยังฉันสงสัยว่าคนจนจะลงรายการที่นี่เพื่ออ่านความคิดเห็นของฉัน :)

สำหรับฉันดูเหมือนว่าเรายังคง จำกัด อยู่กับวิธีคิดแบบ C มากเกินไป ในกระบวนทัศน์ OOP เราเล่นกับวัตถุไม่ใช่ประเภท วัตถุ Const อาจมีแนวคิดที่แตกต่างจากวัตถุที่ไม่ใช่ const โดยเฉพาะในแง่ของตรรกะ-const (ตรงกันข้ามกับ bitwise-const) ดังนั้นแม้ว่าความถูกต้องของ const ของฟังก์ชัน params คือ (บางที) over-ระมัดระวังในกรณีของ PODs มันไม่ได้เป็นเช่นนั้นในกรณีของวัตถุ ถ้าฟังก์ชั่นทำงานกับวัตถุ const มันควรพูดอย่างนั้น พิจารณาข้อมูลโค้ดต่อไปนี้

#include <iostream>

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SharedBuffer {
private:

  int fakeData;

  int const & Get_(int i) const
  {

    std::cout << "Accessing buffer element" << std::endl;
    return fakeData;

  }

public:

  int & operator[](int i)
  {

    Unique();
    return const_cast<int &>(Get_(i));

  }

  int const & operator[](int i) const
  {

    return Get_(i);

  }

  void Unique()
  {

    std::cout << "Making buffer unique (expensive operation)" << std::endl;

  }

};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NonConstF(SharedBuffer x)
{

  x[0] = 1;

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ConstF(const SharedBuffer x)
{

  int q = x[0];

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main()
{

  SharedBuffer x;

  NonConstF(x);

  std::cout << std::endl;

  ConstF(x);

  return 0;

}

ป.ล. : คุณอาจยืนยันว่าการอ้างอิง (const) จะเหมาะสมกว่าที่นี่และให้พฤติกรรมแบบเดียวกัน ก็ใช่ แค่ให้ภาพที่แตกต่างจากสิ่งที่ฉันเห็นที่อื่น ...

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.