คง const เทียบกับ #define


212

การใช้static constvars ดีกว่า#definepreprocessor หรือไม่ หรืออาจจะขึ้นอยู่กับบริบท

ข้อดี / ข้อเสียของแต่ละวิธีคืออะไร?


14
สก็อตเมเยอร์สครอบคลุมเรื่องนี้อย่างมากและทั่วถึง ไอเท็มชิ้นที่ 2 ของเขาใน "Effective C ++ Third Edition" สองกรณีพิเศษ (1) คงที่ const เป็นที่ต้องการภายในขอบเขตชั้นเรียนสำหรับค่าคงที่เฉพาะคลาส (2) เนมสเปซหรือ const ขอบเขตที่ไม่ระบุชื่อเป็นที่ต้องการมากกว่า #define
Eric

2
ฉันชอบ Enums เพราะมันเป็นลูกผสมของทั้งคู่ ไม่ใช้พื้นที่เว้นเสียแต่ว่าคุณจะสร้างตัวแปรขึ้นมา หากคุณเพียงต้องการใช้เป็นค่าคงที่ Enum เป็นตัวเลือกที่ดีที่สุด มีประเภทความปลอดภัยใน C / C ++ 11 std และยังคงที่สมบูรณ์แบบ #define เป็นประเภทที่ไม่ปลอดภัย const ใช้พื้นที่ว่างหากคอมไพเลอร์ไม่สามารถปรับให้เหมาะสม
siddhusingh

1
การตัดสินใจของฉันว่าจะใช้#defineหรือstatic const(สตริง) คือการขับเคลื่อนด้วยการเริ่มต้นด้าน (มันไม่ได้กล่าวถึงผ่านคำตอบด้านล่าง): ถ้าคงถูกนำมาใช้ภายในหน่วยรวบรวมโดยเฉพาะอย่างยิ่งเท่านั้นแล้วฉันไปกับstatic constการใช้งานผมอื่น#define- หลีกเลี่ยงการสั่งซื้อแบบคงที่เริ่มต้นความล้มเหลว isocpp.org/wiki/faq/ctors#static-init-order
Martin Dvorak

ถ้าconst, constexprหรือenumหรือรูปแบบการทำงานในกรณีของคุณแล้วชอบมันไป#define
Phil1970

@MartinDvorak " หลีกเลี่ยงความล้มเหลวในการเริ่มต้นคำสั่งคงที่ " นั่นเป็นปัญหาสำหรับค่าคงที่อย่างไร?
curiousguy

คำตอบ:


139

ส่วนตัวฉันเกลียดชังตัวประมวลผลล่วงหน้าดังนั้นฉันจะไปด้วยconstเสมอ

ข้อได้เปรียบหลักของ a #defineคือมันไม่ต้องใช้หน่วยความจำในการจัดเก็บในโปรแกรมของคุณเนื่องจากมันเป็นเพียงการแทนที่ข้อความบางส่วนด้วยค่าที่แท้จริง นอกจากนี้ยังมีข้อดีที่ไม่มีประเภทดังนั้นจึงสามารถใช้สำหรับค่าจำนวนเต็มใด ๆ โดยไม่ต้องสร้างคำเตือน

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

ฉันไม่รู้เหมือนกันว่าคุณกำลังทำอะไรกับส่วน " static" หากคุณกำลังประกาศทั่วโลกฉันใส่ไว้ใน namespace staticที่ไม่ระบุชื่อแทนการใช้ ตัวอย่างเช่น

namespace {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}

8
ค่าคงที่สตริงโดยเฉพาะเป็นหนึ่งในค่าที่อาจได้รับประโยชน์จากการเป็น#defined อย่างน้อยถ้าสามารถใช้เป็น "หน่วยการสร้าง" สำหรับค่าคงที่สตริงที่ใหญ่กว่า ดูคำตอบของฉันสำหรับตัวอย่าง
AnT

62
#defineประโยชน์จากการไม่ใช้หน่วยความจำใด ๆ ที่ไม่ถูกต้อง ว่า "60" ตัวอย่างจะต้องมีการเก็บไว้ที่อื่นโดยไม่คำนึงถึงถ้ามันหรือstatic const #defineที่จริงแล้วฉันเคยเห็นคอมไพเลอร์ที่ใช้ #define ทำให้เกิดการใช้หน่วยความจำขนาดใหญ่ (อ่านอย่างเดียว) และสแตติก const ใช้หน่วยความจำที่ไม่ต้องการ
Gilad Naor

3
#define เหมือนกับว่าคุณพิมพ์มันดังนั้นมันจึงไม่ได้มาจากความทรงจำ
สาธุคุณ

27
@theReverend ค่าตัวอักษรได้รับการยกเว้นจากการใช้ทรัพยากรเครื่องอย่างใดหรือไม่? ไม่พวกเขาอาจใช้วิธีที่แตกต่างกันบางทีมันอาจจะไม่ปรากฏบนสแต็กหรือกอง แต่ในบางจุดโปรแกรมจะโหลดลงในหน่วยความจำพร้อมกับค่าทั้งหมดที่รวบรวมไว้
Sqeaky

13
@ gilad-naor, คุณพูดถูก แต่โดยทั่วไปจำนวนเต็มน้อยเช่น 60 อาจจะเป็นข้อยกเว้นบางส่วน ชุดคำสั่งบางชุดมีความสามารถในการเข้ารหัสจำนวนเต็มหรือชุดย่อยของจำนวนเต็มโดยตรงในสตรีมคำสั่ง ตัวอย่างเช่นMIPเพิ่มทันที ( cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/addi.html ) ในกรณีแบบนี้จำนวนเต็ม #defined อาจกล่าวได้ว่าไม่มีที่ว่างเนื่องจากในไบนารีที่คอมไพล์มันมีบิตสำรองไว้สองสามคำสั่งซึ่งต้องมีอยู่แล้ว
ahcox

242

ข้อดีและข้อเสียระหว่าง#defines, consts และ (สิ่งที่คุณลืม) enumขึ้นอยู่กับการใช้งาน:

  1. enums:

    • เป็นไปได้สำหรับค่าจำนวนเต็มเท่านั้น
    • ปัญหาการชนกันของขอบเขต / ตัวระบุที่จัดการอย่างเหมาะสมโดยเฉพาะอย่างยิ่งในคลาส C ++ 11 enum ที่การแจกแจงenum class Xถูก disambiguated ตามขอบเขตX::
    • พิมพ์อย่างยิ่ง แต่มีขนาดใหญ่เพียงพอที่ลงชื่อหรือไม่ได้ลงชื่อซึ่งคุณไม่มีการควบคุมใน C ++ 03 (แม้ว่าคุณสามารถระบุเขตข้อมูลขนาดเล็กที่ควรจะบรรจุถ้า enum เป็นสมาชิกของ struct / class / union) ในขณะที่ค่าเริ่มต้น C ++ 11 เป็นintแต่สามารถกำหนดอย่างชัดเจนโดยโปรแกรมเมอร์
    • ไม่สามารถระบุที่อยู่ได้ - มีเพียงอันเดียวเนื่องจากค่าการแจงนับจะถูกแทนที่แบบอินไลน์อย่างมีประสิทธิภาพ ณ จุดใช้งาน
    • ข้อ จำกัด ในการใช้งานที่เข้มงวดขึ้น (เช่นการเพิ่มขึ้น - template <typename T> void f(T t) { cout << ++t; }จะไม่รวบรวมแม้ว่าคุณสามารถล้อม Enum ลงในคลาสด้วย Constructor ตัวดำเนินการแคสต์และโอเปอเรเตอร์ที่ผู้ใช้กำหนด)
    • ค่าคงที่แต่ละประเภทที่นำมาจาก enum ที่ปิดล้อมดังนั้นtemplate <typename T> void f(T)รับค่าอินสแตนซ์ที่แตกต่างกันเมื่อผ่านค่าตัวเลขเดียวกันจากค่าจำนวนต่างกันซึ่งทั้งหมดจะแตกต่างจากค่าf(int)อินสแตนซ์จริง รหัสวัตถุของแต่ละฟังก์ชั่นอาจเหมือนกัน (ไม่สนใจที่อยู่ออฟเซ็ต) แต่ฉันไม่คิดว่าคอมไพเลอร์ / ลิงเกอร์จะกำจัดสำเนาที่ไม่จำเป็นออกไปแม้ว่าคุณจะสามารถตรวจสอบคอมไพเลอร์ / ลิงเกอร์ของคุณได้
    • แม้จะมี typeof / decltype ไม่สามารถคาดหวัง numeric_limits ให้ข้อมูลเชิงลึกที่เป็นประโยชน์ในชุดของค่าที่มีความหมายและการรวมกัน (ที่จริง "ตามกฎหมาย" รวมกันไม่ได้ notated แม้ในรหัสที่มาให้พิจารณาenum { A = 1, B = 2 }- เป็นA|B"ตามกฎหมาย" จากตรรกะโปรแกรม มุมมอง?)
    • ชื่อพิมพ์ของ Enum อาจปรากฏในหลาย ๆ ที่ใน RTTI, คอมไพเลอร์ข้อความ ฯลฯ - อาจมีประโยชน์, อาจทำให้งงงวย
    • คุณไม่สามารถใช้การแจงนับหากไม่มีหน่วยการแปลที่เห็นค่าจริง ๆ ซึ่งหมายความว่า enums ในไลบรารี่ API ต้องการค่าที่เปิดเผยในส่วนหัวและmakeและเครื่องมือการคอมไพล์ที่ใช้การประทับเวลาอื่น ๆ จะทำให้เกิดการคอมไพล์ไคลเอนต์ใหม่ )

  1. consts:

    • ปัญหาการชนกันของตัวระบุ / การระบุอย่างไม่ถูกต้องได้รับการจัดการอย่างดี
    • ประเภทที่แข็งแกร่งเดี่ยวและผู้ใช้ระบุ
      • คุณอาจพยายามที่จะ "พิมพ์" #defineaa #define S std::string("abc")แต่คงหลีกเลี่ยงการสร้างซ้ำของขมับที่แตกต่างกันในแต่ละจุดของการใช้งาน
    • One Definition Rule แทรกซ้อน
    • สามารถใช้ที่อยู่สร้างการอ้างอิง const ให้กับพวกเขา ฯลฯ
    • ส่วนใหญ่คล้ายกับconstค่าที่ไม่ทำให้การทำงานและผลกระทบลดลงหากสลับไปมาระหว่างทั้งสอง
    • สามารถวางค่าไว้ในไฟล์การนำไปใช้งานช่วยให้สามารถรวบรวม recompile และลิงก์ไคลเอ็นต์เพื่อรับการเปลี่ยนแปลง

  1. #defines:

    • ขอบเขต "ทั่วโลก" / มีแนวโน้มที่จะมีการขัดแย้งกันมากขึ้นซึ่งสามารถสร้างปัญหาการรวบรวมที่ยากต่อการแก้ไขและผลการดำเนินการที่ไม่คาดคิดมากกว่าข้อความแสดงข้อผิดพลาดที่มีสติ การบรรเทานี้ต้อง:
      • ยาวระบุชัดเจนและ / หรือประสานงานจากส่วนกลางและการเข้าถึงพวกเขาไม่สามารถได้รับประโยชน์จากการจับคู่ที่ใช้ / ปัจจุบัน / เนมสเปค - เงยหน้าขึ้นมองนิรนามเนมสเปซ ฯลฯ
      • ในขณะที่วิธีปฏิบัติที่ดีที่สุดที่ทรัมป์อนุญาตให้ตัวระบุพารามิเตอร์เทมเพลตเป็นตัวอักษรตัวพิมพ์ใหญ่ตัวเดียว (อาจตามด้วยตัวเลข) การใช้ตัวระบุอื่น ๆ ที่ไม่มีตัวอักษรตัวพิมพ์เล็กสงวนไว้ตามปกติสำหรับและคาดหวังว่า ส่วนหัว) นี่เป็นสิ่งสำคัญสำหรับการใช้งานตัวประมวลผลล่วงหน้าระดับองค์กรเพื่อให้สามารถจัดการได้ ห้องสมุดบุคคลที่สามสามารถคาดหวังได้ว่าจะปฏิบัติตาม การสังเกตนี้แสดงถึงการโยกย้ายของ const ที่มีอยู่หรือ enums ไปยัง / จากการกำหนดที่เกี่ยวข้องกับการเปลี่ยนแปลงในตัวพิมพ์ใหญ่และดังนั้นจึงต้องแก้ไขไปยังรหัสที่มาของลูกค้ามากกว่าคอมไพล์ "ง่าย" (โดยส่วนตัวแล้วฉันใช้ตัวอักษรตัวแรกของ enumerations แต่ไม่ใช่ consts ดังนั้นฉันจะได้รับการโยกย้ายระหว่างสองคนนี้ด้วย - อาจถึงเวลาคิดใหม่)
    • การคอมไพล์เวลาที่เป็นไปได้มากขึ้น: การเรียงต่อสตริงตามตัวอักษร, การทำให้สตริง (ใช้ขนาดของสตริง), การต่อข้อมูลเป็นตัวระบุ
      • ข้อเสียคือว่าได้รับ#define X "x"และบาง Ala การใช้งานของลูกค้า"pre" X "post"ถ้าคุณต้องการหรือต้องการที่จะทำให้ X รันไทม์เปลี่ยนแปลงตัวแปรมากกว่าคงที่คุณบังคับให้แก้ไขรหัสลูกค้า (มากกว่าแค่ recompilation) ในขณะที่การเปลี่ยนแปลงที่ง่ายจากหนึ่งconst char*หรือconst std::stringให้พวกเขา บังคับให้ผู้ใช้รวมการดำเนินการเรียงต่อกัน (เช่น"pre" + X + "post"สำหรับstring)
    • ไม่สามารถใช้sizeofโดยตรงกับตัวอักษรตัวเลขที่กำหนดได้
    • ไม่ได้พิมพ์ (GCC ไม่เตือนถ้าเปรียบเทียบกับunsigned)
    • โซ่คอมไพเลอร์ / ลิงเกอร์ / ดีบั๊กบางอันอาจไม่แสดงตัวระบุดังนั้นคุณจะลดการมองที่ "ตัวเลขเวทมนตร์" (สตริงอะไรก็ตาม ... )
    • ไม่สามารถใช้ที่อยู่
    • ค่าทดแทนไม่จำเป็นต้องถูกกฎหมาย (หรือไม่ต่อเนื่อง) ในบริบทที่ #define ถูกสร้างขึ้นเนื่องจากมีการประเมินในแต่ละจุดใช้งานดังนั้นคุณสามารถอ้างอิงวัตถุที่ยังไม่ได้ประกาศขึ้นอยู่กับ "การนำไปใช้" ที่ไม่จำเป็น รวมไว้ล่วงหน้าสร้าง "ค่าคงที่" เช่น{ 1, 2 }ที่สามารถใช้ในการเริ่มต้นอาร์เรย์หรือ#define MICROSECONDS *1E-6อื่น ๆ ( แน่นอนไม่แนะนำนี้!)
    • สิ่งพิเศษบางอย่างเช่น__FILE__และ__LINE__สามารถรวมอยู่ในการทดแทนแมโคร
    • คุณสามารถทดสอบการมีอยู่และความคุ้มค่าใน#ifข้อความสั่งแบบมีเงื่อนไขรวมถึงรหัส (มีประสิทธิภาพมากกว่าการโพสต์ preprocessing "if" เนื่องจากรหัสไม่จำเป็นต้อง#undefคอมไพล์ถ้าไม่เลือกโดย preprocessor) ใช้-ine, redefine เป็นต้น
    • ข้อความที่ถูกแทนที่จะต้องได้รับการเปิดเผย:
      • ในหน่วยการแปลที่ใช้โดยซึ่งหมายความว่ามาโครในไลบรารีสำหรับการใช้งานของลูกค้าจะต้องอยู่ในส่วนหัวดังนั้นmakeและเครื่องมือการคอมไพล์ที่ใช้การประทับเวลาอื่น ๆ จะเรียกใช้การคอมไพล์ไคลเอ็นต์ใหม่เมื่อมีการเปลี่ยนแปลง (ไม่ดี!)
      • หรือบนบรรทัดคำสั่งที่จำเป็นต้องใช้ความระมัดระวังมากขึ้นเพื่อให้แน่ใจว่ารหัสไคลเอนต์ถูกคอมไพล์ใหม่ (เช่น Makefile หรือสคริปต์ที่ให้คำจำกัดความควรแสดงรายการเป็นการอ้างอิง)

ความคิดเห็นส่วนตัวของฉัน:

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


1
คำตอบที่ยอดเยี่ยม หนึ่งนิดหน่อยเล็ก ๆ : บางครั้งฉันใช้ enums ท้องถิ่นที่ไม่ได้อยู่ในส่วนหัวเพียงเพื่อความชัดเจนของรหัสเช่นในเครื่องรัฐขนาดเล็กและเช่นนั้น ดังนั้นพวกเขาไม่จำเป็นต้องอยู่ในส่วนหัวตลอดเวลา
kert

ข้อดีและข้อเสียต่างกันไปฉันอยากจะดูตารางเปรียบเทียบ
Unknown123

@ Unknown123: อย่าลังเลที่จะโพสต์หนึ่ง - ฉันไม่รังเกียจถ้าคุณตัดคะแนนใด ๆ ที่คุณรู้สึกว่าคู่ควรกับที่นี่ ไชโย
โทนี่เดล

48

หากนี่คือคำถาม C ++ และกล่าวถึง#defineเป็นทางเลือกแสดงว่าเป็นค่าคงที่ "global" (เช่นขอบเขตไฟล์) ไม่ใช่ค่าสมาชิกระดับชั้น เมื่อมันมาถึงค่าคงที่ดังกล่าวใน C ++ static constซ้ำซ้อน ใน C ++ มีการเชื่อมโยงภายในโดยเริ่มต้นและมีจุดใดในการประกาศให้พวกเขาconst staticดังนั้นจึงเป็นจริงเกี่ยวกับconst#define

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

ค่าคงที่สตริง BTW เป็นตัวอย่างหนึ่งของข้อยกเว้นดังกล่าว ด้วย#defineค่าคงที่สตริง d เราสามารถใช้คุณสมบัติการต่อเวลาคอมไพล์ของคอมไพเลอร์ C / C ++ ได้เช่นเดียวกับใน

#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"

const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;

ป.ล. อีกครั้งในกรณีเมื่อมีคนกล่าวถึงstatic constทางเลือก#defineก็มักจะหมายความว่าพวกเขากำลังพูดถึง C ไม่ใช่ C ++ ฉันสงสัยว่าคำถามนี้ถูกแท็กอย่างถูกต้องหรือไม่ ...


1
" ไม่มีเหตุผลที่จะชอบ #define " เหนืออะไร ตัวแปรคงที่กำหนดไว้ในไฟล์ส่วนหัว?
curiousguy

9

#define สามารถนำไปสู่ผลลัพธ์ที่ไม่คาดคิด:

#include <iostream>

#define x 500
#define y x + 5

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

แสดงผลลัพธ์ที่ไม่ถูกต้อง:

y is 505
z is 510

อย่างไรก็ตามหากคุณแทนที่ด้วยค่าคงที่:

#include <iostream>

const int x = 500;
const int y = x + 5;

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

มันออกผลลัพธ์ที่ถูกต้อง:

y is 505
z is 1010

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


1
ฉันมีผลลัพธ์ที่ไม่คาดคิดแตกต่างกัน: yมีค่า5500การต่อเชื่อมแบบx
รหัสกับ Hammer

5

การใช้ const คงที่ก็เหมือนกับการใช้ตัวแปร const อื่น ๆ ในรหัสของคุณ ซึ่งหมายความว่าคุณสามารถติดตามได้ทุกที่ที่ข้อมูลมาตรงข้ามกับ #define ที่จะถูกแทนที่ในรหัสในกระบวนการรวบรวมล่วงหน้า

คุณอาจต้องการดู C + คำถามที่พบบ่อย Lite สำหรับคำถามนี้: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7


4
  • คงที่ const พิมพ์ (มันมีประเภท) และสามารถตรวจสอบได้โดยคอมไพเลอร์เพื่อความถูกต้อง, นิยามใหม่ ฯลฯ
  • #define สามารถ redifined ไม่ได้กำหนดอะไรก็ได้

โดยปกติแล้วคุณควรเลือก const ที่คงที่ ไม่มีข้อเสีย prprocessor ส่วนใหญ่จะใช้สำหรับการรวบรวมตามเงื่อนไข (และบางครั้งสำหรับ trics ที่สกปรกจริงๆอาจจะ)


3

การกำหนดค่าคงที่โดยใช้คำสั่ง preprocessor #defineไม่แนะนำให้ใช้ไม่เพียง แต่ในC++แต่ยังรวมCถึง ค่าคงที่เหล่านี้จะไม่มีชนิด แม้แต่ในCก็เสนอให้ใช้constสำหรับค่าคงที่



2

ต้องการใช้คุณสมบัติภาษามากกว่าเครื่องมือเพิ่มเติมบางอย่างเช่นโปรเซสเซอร์ล่วงหน้า

ES.31: อย่าใช้มาโครสำหรับค่าคงที่หรือ "ฟังก์ชั่น"

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

จากแนวทางหลักของ C ++


0

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

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