Const ก่อนหรือ const หลัง?


146

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

const Object* obj; // can't change data
Object* const obj; // can't change pointer
const Object* const obj; // can't change data or pointer

อย่างไรก็ตามคุณยังสามารถใช้ไวยากรณ์:

Object const *obj; // same as const Object* obj;

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

ที่สำคัญกว่านั้นคือทำไมมีวิธีการระบุconstข้อมูลที่ถูกต้องสองวิธีและในสถานการณ์ใดที่คุณต้องการหรือต้องการอีกวิธีหนึ่งหากมี

แก้ไข:

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

นี่เป็นกรณีใน C และถ้าอย่างนั้นฉันจะสมมุติ?


9
ในแมโครเสมอเพิ่มconst หลังจากชนิดเช่น#define MAKE_CONST(T) T constแทน#define MAKE_CONST(T) const Tเพื่อให้MAKE_CONST(int *)ถูกต้องจะขยายตัวออกไปแทนint * const const int *
jotik

7
ฉันได้เห็นทั้งสองรูปแบบที่เรียกว่า "กลุ่มทิศตะวันออก" และ "กลุ่มตะวันตก"
Tom Anderson

14
@ TomAnderson แต่จริงๆแล้วมันควรจะเป็น "East const" และ "const west"
YSC

คำตอบ:


96

เหตุใดจึงมีวิธีการระบุconstข้อมูลที่ถูกต้องสองวิธีและในสถานการณ์ใดที่คุณต้องการหรือต้องการอีกวิธีหนึ่งหากมี

โดยพื้นฐานแล้วเหตุผลที่ว่าตำแหน่งของconstspecifier ภายในเครื่องหมายดอกจันไม่สำคัญว่าไวยากรณ์ C นั้นถูกกำหนดโดย Kernighan และ Ritchie

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

เนื่องจากความหมายเชิงความหมายไม่เปลี่ยนแปลงหากตัวระบุconstปรากฏขึ้นก่อนหรือหลังตัวระบุประเภทจึงได้รับการยอมรับทั้งสองทาง

กรณีที่คล้ายกันเกิดขึ้นเมื่อประกาศตัวชี้ฟังก์ชันโดยที่:

  • void * function1(void)ประกาศฟังก์ชั่นซึ่งผลตอบแทนvoid *,

  • void (* function2)(void)ประกาศตัวชี้ฟังก์ชั่นvoidฟังก์ชั่นซึ่งผลตอบแทน

สิ่งที่ควรสังเกตอีกครั้งคือไวยากรณ์ภาษารองรับตัวแยกวิเคราะห์จากซ้ายไปขวา


7
Kernighan ร่วมเขียนหนังสือเล่มนี้ แต่ไม่ได้มีส่วนร่วมในการออกแบบของ C เพียง Ritchie
Tom Zych

8
ฉันไม่สามารถจำได้ว่าเป็นที่หนึ่ง ขอบคุณคำอธิบายของคุณฉันในที่สุดก็มี mnemotechnic จำได้ ขอบคุณ! ก่อน*คอมไพเลอร์ตัวแยกวิเคราะห์ไม่ทราบว่ามันเป็นตัวชี้จึงเป็น const สำหรับค่าข้อมูล หลังจาก * มันเกี่ยวข้องกับพอยน์เตอร์คงที่ สุกใส และในที่สุดก็จะอธิบายว่าทำไมฉันจะทำเช่นเดียวกับconst char char const
Vit Bernatik

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

3
"เนื่องจากความหมายเชิงความหมายไม่เปลี่ยนแปลงหากตัวระบุ const ปรากฏก่อนหรือหลังตัวระบุชนิดจึงเป็นที่ยอมรับทั้งสองวิธี" การใช้เหตุผลแบบวงกลมนั้นไม่ใช่หรือ คำถามคือทำไมความหมายเชิงความหมายถูกกำหนดเช่นนั้นดังนั้นฉันไม่คิดว่าประโยคนี้มีส่วนช่วยอะไรเลย
Don Hatch

1
@donhatch คุณต้องจำไว้ว่าเมื่อเทียบกับทุกวันนี้และสมมติฐานที่เราทำขึ้นอยู่กับความคุ้นเคยกับการออกแบบภาษาโปรแกรมที่ดีภาษาก็ค่อนข้างใหม่ นอกจากนี้ไม่ว่าใครจะมีภาษาที่อนุญาตหรือ จำกัด เป็นการตัดสินคุณค่า เช่น python ควรมี++โอเปอเรเตอร์หรือไม่ "ประโยค" อิโมะช่วยให้ฉันรู้ว่าไม่มีเหตุผลใดที่นอกเหนือไปจากที่พวกเขาทำได้ บางทีพวกเขาอาจจะเลือกที่แตกต่างกันในวันนี้ / อาจจะไม่
ragerdl

75

กฎคือ:

const ใช้กับสิ่งที่เหลืออยู่ของมัน หากไม่มีสิ่งใดอยู่ทางซ้ายก็จะใช้กับสิ่งที่ถูกต้อง

ฉันชอบใช้ const ทางด้านขวาของสิ่งที่จะ const เพียงเพราะมันเป็น "ดั้งเดิม" วิธีกำหนด const

แต่ฉันคิดว่านี่เป็นมุมมองส่วนตัว


18
ฉันชอบที่จะวางไว้ทางซ้าย แต่ฉันคิดว่าการวางไว้ที่ด้านขวานั้นเหมาะสมกว่า โดยทั่วไปคุณอ่านชนิดใน C ++ จากขวาไปซ้ายตัวอย่างเช่นObject const *ตัวชี้ไปยังวัตถุ const หากคุณวางconstด้านซ้ายมันจะอ่านเป็นตัวชี้ไปยังวัตถุที่เป็น const ซึ่งไหลไม่ดีนัก
Collin Dauphinee

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

1
@ ภายใต้ฉันเชื่อว่ามันเป็นแนวทางมากกว่ากฎและฉันได้ยินบ่อยครั้งว่าวิธีที่ผู้แปลจะตีความมัน ... ฉันเข้าใจว่ามันทำงานอย่างไรฉันจึงอยากรู้เกี่ยวกับกระบวนการคิดที่อยู่เบื้องหลัง ตัดสินใจที่จะสนับสนุนทั้งสองวิธี
AJG85

3
@HeathHunnicutt กฎที่มีอยู่ แต่มันก็เป็นเพียงเล็ก ๆ น้อย ๆ ที่มีความซับซ้อนมากขึ้น: c-faq.com/decl/spiral.anderson.html
imallett

2
@HeathHunnicutt: กฎหมุนวนเป็นเวอร์ชั่นขยายของความคิดเห็นแรกของผู้วิจารณ์ "คุณมักจะอ่านประเภทใน [C /] C ++ จากขวาไปซ้าย" ฉันคิดว่าคุณขัดแย้งกับเรื่องนี้ อย่างไรก็ตามฉันคิดว่าคุณอาจได้รับการอ้างถึงคำตอบเอง
imallett

57

ฉันชอบไวยากรณ์ที่สอง มันช่วยให้ฉันติดตาม 'อะไร' ที่เป็นค่าคงที่โดยการอ่านการประกาศประเภทจากขวาไปซ้าย:

Object * const obj;        // read right-to-left:  const pointer to Object
Object const * obj;        // read right-to-left:  pointer to const Object
Object const * const obj;  // read right-to-left:  const pointer to const Object

3
เผง A "ชี้คงไปยังวัตถุที่คงที่" เป็นไม่ได้Object const* const const const Object*"const" ไม่สามารถอยู่ทางซ้ายยกเว้นในกรณีพิเศษที่ผู้คนจำนวนมากรักมันอย่างแน่นอน (ดู Heath ด้านบน)
cdunn2001

38

ลำดับของคำหลักในการประกาศไม่ใช่สิ่งที่แก้ไขทั้งหมด มีหลายทางเลือกในการ "หนึ่งคำสั่งซื้อจริง" แบบนี้

int long const long unsigned volatile i = 0;

หรือควรจะเป็น

volatile unsigned long long int const i = 0;

??


25
+1 สำหรับคำจำกัดความที่สับสนโดยสิ้นเชิงของตัวแปรอย่างง่าย :)
Xeo

@rubenvb - ใช่น่าเสียดายที่พวกเขาเป็น ไวยากรณ์เพิ่งบอกว่า a decl-specifier-seqคือลำดับของdecl-specifiers ไม่มีการสั่งซื้อจากไวยากรณ์และจำนวนครั้งสำหรับคำหลักแต่ละคำจะถูก จำกัด โดยกฎความหมายบางอย่างเท่านั้น (คุณสามารถมีหนึ่งconstlong
Bo Persson

3
@rubenvb - ใช่unsignedเป็นชนิดเดียวกับและunsigned int เป็นอีกประเภทหนึ่งเช่นเดียวกับและ เห็นรูปแบบ? int unsignedunsigned longunsigned long intint long unsigned
โบเพอร์สัน

2
@Bo: ;)ผมเห็นระเบียบจะต้องมีสามถึงเห็นรูปแบบ ตกลงขอบคุณ
rubenvb

1
คุณเคยสามารถเพิ่มstaticความสับสนของคำ แต่เมื่อเร็ว ๆ นี้มีนักแปลที่บ่นว่าstaticต้องมาก่อน
Mark Lakata

8

กฎข้อแรกคือการใช้รูปแบบใดมาตรฐานการเข้ารหัสท้องถิ่นของคุณต้องการ หลังจากนั้น: การใส่constด้านหน้านำไปสู่การสิ้นสุดความสับสนเมื่อ typedefs มีส่วนร่วมเช่น:

typedef int* IntPtr;
const IntPtr p1;   // same as int* const p1;

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


ฉันคิดว่านี่อาจเป็นเหตุผลว่าทำไมเราถึงไม่พิมพ์พอยน์เตอร์ในมาตรฐานที่กำหนดไว้ที่ร้านนี้
AJG85

1
นโยบายของฉัน (เมื่อฉันตัดสินใจคนเดียว) คือการกำหนด const after (เพื่อประโยชน์ในการเชื่อมโยงกัน) และไม่ใช้ typedefs เพื่อชี้ (หรือพิมพ์ทั่วไปมาก) :-) และ BTW, string :: iterator vs. string :: const_iterator น่าจะถูกนำมาพิจารณาในการตัดสินใจของคุณเช่นกัน (เพื่อสับสนสิ่ง :-) ไม่มีคำตอบที่ถูกต้อง)
James Kanze

อ่าใช่ฉันจะได้รวมพฤติกรรมของconst std::string::const_iteratorเช่นกันสำหรับการวัดที่ดี;)
AJG85

2
@JamesKanze - เดี๋ยวก่อนมาช่วยฉันที่นี่ ... ฉันไม่เห็นความสับสนในตัวอย่างที่โพสต์ มีอะไรอีกที่const IntPtr p1อาจหมายถึงสิ่งอื่นนอกเหนือจาก "ตัวชี้จำนวนเต็มคงที่" (เช่น "ตัวชี้คงที่เป็นจำนวนเต็ม") ไม่มีใครในใจที่ถูกต้องแม้จะไม่รู้ว่าIntPtrจะนิยามอย่างไรก็คิดว่าp1มันไม่แน่นอน และสำหรับเรื่องนั้นทำไมทุกคนจะคิดผิดว่า*p1ไม่เปลี่ยนรูป? ยิ่งไปกว่านั้นการวาง const ไว้ที่อื่น (เช่นIntPtr const p1) จะไม่เปลี่ยนความหมายเลย
ทอดด์เลห์แมน

3
@ToddLehman คุณอาจไม่เห็นความสับสน แต่โปรแกรมเมอร์ C ++ ส่วนใหญ่ทำและผิดพลาดอย่างเป็นระบบ (ไม่ต้องสงสัยเลยว่าช่วยด้วยสิ่งต่าง ๆ เช่นstd::vector<T>::const_iteratorที่มันไม่ใช่ตัววนซ้ำซึ่งเป็น const แต่สิ่งที่ชี้ไป)
James Kanze

7

มีเหตุผลทางประวัติศาสตร์ที่ยอมรับได้ทั้งซ้ายหรือขวา Stroustrup ได้เพิ่ม const ใน C ++ ในปี 1983แต่มันไม่ได้ทำให้เป็น C จนกระทั่ง C89 / C90

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

int getInt() const;

1
... อืม "เหตุผลที่ดี" นั้นไม่น่าเชื่อมากนักเพราะสถานที่อื่น ๆ ที่เป็นไปได้สำหรับ "const" จะไม่หมายถึงสิ่งเดียวกัน const int& getInt(); int& const getInt();
มาสโทร

1
@Maestro: ฉันแนะนำว่าint const& getInt();ดีกว่าที่เทียบเท่าconst int& getInt();ในขณะint& const getInt();ที่คุณเปรียบเทียบกับซ้ำซ้อน (อ้างอิงเป็น const แล้ว) แม้ว่ากฎหมายและมักจะให้คำเตือน อย่างไรก็ตาม const ต่อการทำงานของสมาชิกเปลี่ยนแปลงthisตัวชี้ในการทำงานจากการFoo* const Foo const* const
Nick Westgate

constในฟังก์ชั่นสมาชิกไม่ได้มีความหมายเหมือนกัน - หรือvoid set(int)&;เป็นการอ้างอิงถึงฟังก์ชั่นบางอย่าง?
Davis Herring
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.