ฉันควรใช้มาโครที่ไหนและฉันควรใช้constexprที่ไหน โดยพื้นฐานแล้วพวกเขาไม่เหมือนกันหรือ?
#define MAX_HEIGHT 720
เทียบกับ
constexpr unsigned int max_height = 720;
ฉันควรใช้มาโครที่ไหนและฉันควรใช้constexprที่ไหน โดยพื้นฐานแล้วพวกเขาไม่เหมือนกันหรือ?
#define MAX_HEIGHT 720
เทียบกับ
constexpr unsigned int max_height = 720;
คำตอบ:
โดยพื้นฐานแล้วพวกเขาไม่เหมือนกันหรือ?
ไม่ไม่อย่างแน่นอน ไม่ได้ใกล้เคียง.
นอกเหนือจากข้อเท็จจริงที่ว่ามาโครของคุณเป็นint
และconstexpr unsigned
เป็นของคุณunsigned
แล้วยังมีข้อแตกต่างที่สำคัญและมาโครมีข้อดีเพียงอย่างเดียว
มาโครถูกกำหนดโดยตัวประมวลผลล่วงหน้าและจะถูกแทนที่ลงในโค้ดทุกครั้งที่เกิดขึ้น ตัวประมวลผลก่อนเป็นใบ้และไม่เข้าใจไวยากรณ์ C ++ หรือความหมาย มาโครจะไม่สนใจขอบเขตเช่นเนมสเปซคลาสหรือบล็อกฟังก์ชันดังนั้นคุณจึงไม่สามารถใช้ชื่อสำหรับสิ่งอื่นในไฟล์ต้นฉบับได้ นั่นไม่เป็นความจริงสำหรับค่าคงที่ที่กำหนดให้เป็นตัวแปร C ++ ที่เหมาะสม:
#define MAX_HEIGHT 720
constexpr int max_height = 720;
class Window {
// ...
int max_height;
};
เป็นเรื่องปกติที่จะมีการเรียกตัวแปรสมาชิกmax_height
เนื่องจากเป็นสมาชิกชั้นเรียนจึงมีขอบเขตที่แตกต่างกันและแตกต่างจากตัวแปรที่ขอบเขตเนมสเปซ หากคุณพยายามใช้ชื่อMAX_HEIGHT
ของสมาชิกซ้ำตัวประมวลผลก่อนจะเปลี่ยนเป็นเรื่องไร้สาระที่จะไม่รวบรวม:
class Window {
// ...
int 720;
};
นี่คือเหตุผลที่คุณต้องให้มาโครUGLY_SHOUTY_NAMES
เพื่อให้แน่ใจว่ามันโดดเด่นและคุณสามารถระมัดระวังในการตั้งชื่อเพื่อหลีกเลี่ยงการปะทะกัน หากคุณไม่ใช้มาโครโดยไม่จำเป็นคุณก็ไม่ต้องกังวลเรื่องนั้น (และไม่ต้องอ่านSHOUTY_NAMES
)
หากคุณต้องการค่าคงที่ภายในฟังก์ชันคุณไม่สามารถทำได้ด้วยมาโครเนื่องจากตัวประมวลผลล่วงหน้าไม่ทราบว่าฟังก์ชันคืออะไรหรืออยู่ภายในฟังก์ชันนั้นหมายความว่าอย่างไร หากต้องการ จำกัด มาโครเฉพาะบางส่วนของไฟล์คุณต้องใช้#undef
มันอีกครั้ง:
int limit(int height) {
#define MAX_HEIGHT 720
return std::max(height, MAX_HEIGHT);
#undef MAX_HEIGHT
}
เปรียบเทียบกับสิ่งที่สมเหตุสมผลกว่า:
int limit(int height) {
constexpr int max_height = 720;
return std::max(height, max_height);
}
ทำไมคุณถึงชอบมาโคร?
ตัวแปร constexpr เป็นตัวแปรดังนั้นจึงมีอยู่จริงในโปรแกรมและคุณสามารถทำสิ่งต่างๆของ C ++ ตามปกติเช่นใช้ที่อยู่ของมันและเชื่อมโยงการอ้างอิงกับมัน
รหัสนี้มีพฤติกรรมที่ไม่ได้กำหนดไว้:
#define MAX_HEIGHT 720
int limit(int height) {
const int& h = std::max(height, MAX_HEIGHT);
// ...
return h;
}
ปัญหาคือนั่นMAX_HEIGHT
ไม่ใช่ตัวแปรดังนั้นสำหรับการเรียกstd::max
ชั่วคราวint
จะต้องสร้างโดยคอมไพเลอร์ การอ้างอิงที่ส่งคืนโดยstd::max
อาจอ้างถึงชั่วคราวนั้นซึ่งไม่มีอยู่หลังจากสิ้นสุดคำสั่งนั้นดังนั้นจึงreturn h
เข้าถึงหน่วยความจำที่ไม่ถูกต้อง
ปัญหานั้นไม่มีอยู่ในตัวแปรที่เหมาะสมเนื่องจากมีตำแหน่งคงที่ในหน่วยความจำที่ไม่หายไป:
int limit(int height) {
constexpr int max_height = 720;
const int& h = std::max(height, max_height);
// ...
return h;
}
(ในทางปฏิบัติคุณอาจจะint h
ไม่ประกาศconst int& h
แต่ปัญหาอาจเกิดขึ้นในบริบทที่ละเอียดกว่านี้)
เวลาเดียวที่จะชอบมาโครคือเมื่อคุณต้องการให้พรีโปรเซสเซอร์เข้าใจค่าของมันเพื่อใช้ใน#if
เงื่อนไขเช่น
#define MAX_HEIGHT 720
#if MAX_HEIGHT < 256
using height_type = unsigned char;
#else
using height_type = unsigned int;
#endif
คุณไม่สามารถใช้ตัวแปรได้ที่นี่เนื่องจากตัวประมวลผลล่วงหน้าไม่เข้าใจวิธีอ้างถึงตัวแปรตามชื่อ มันเข้าใจเฉพาะสิ่งพื้นฐานพื้นฐานเท่านั้นเช่นการขยายมาโครและคำสั่งที่ขึ้นต้นด้วย#
(like #include
and #define
and #if
)
หากคุณต้องการค่าคงที่ที่พรีโปรเซสเซอร์สามารถเข้าใจได้คุณควรใช้ตัวประมวลผลล่วงหน้าเพื่อกำหนด หากคุณต้องการค่าคงที่สำหรับรหัส C ++ ปกติให้ใช้รหัส C ++ ปกติ
ตัวอย่างข้างต้นเป็นเพียงการแสดงให้เห็นถึงเงื่อนไขของตัวประมวลผลล่วงหน้า แต่ถึงแม้รหัสนั้นจะหลีกเลี่ยงการใช้ตัวประมวลผลล่วงหน้าได้:
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
constexpr
จำเป็นตัวแปรไม่ใช้หน่วยความจำจนที่อยู่ (ตัวชี้ / อ้างอิง) ถูกนำมา; มิฉะนั้นก็สามารถปรับให้เหมาะสมได้อย่างสมบูรณ์ (และฉันคิดว่าอาจมี Standardese ที่รับประกันได้) ฉันต้องการเน้นย้ำเรื่องนี้เพื่อไม่ให้ผู้คนใช้ ' enum
แฮ็ก' แบบเก่าต่อไปจากความคิดที่เข้าใจผิดว่าเรื่องเล็กน้อยconstexpr
ที่ไม่ต้องการพื้นที่เก็บข้อมูลจะครอบครองบางส่วน
int height
ก็จะเป็นปัญหาเช่นเดียวกับมาโครเนื่องจากขอบเขตของมันเชื่อมโยงกับฟังก์ชันซึ่งโดยพื้นฐานแล้วชั่วคราวด้วย 3. ความคิดเห็นด้านบน "const int & h จะขยายอายุการใช้งานชั่วคราว" ถูกต้อง
limit
ปัญหาคือค่าส่งคืนของstd::max
. 2. ใช่นั่นคือสาเหตุที่ไม่ส่งคืนข้อมูลอ้างอิง 3. ผิดดูลิงค์ coliru ด้านบน
const int& h = max(x, y);
และmax
ส่งคืนตามมูลค่าอายุการใช้งานของมูลค่าที่ส่งคืนจะขยายออกไป ไม่ใช่ตามประเภทผลตอบแทน แต่const int&
เป็นประเภทที่ถูกผูกไว้ สิ่งที่ฉันเขียนนั้นถูกต้อง
โดยทั่วไปคุณควรใช้constexpr
ทุกครั้งที่ทำได้และมาโครก็ต่อเมื่อไม่มีวิธีแก้ปัญหาอื่น ๆ
มาโครเป็นการแทนที่โค้ดอย่างง่ายและด้วยเหตุนี้จึงมักสร้างความขัดแย้ง (เช่น windows.h max
macro vs std::max
) นอกจากนี้มาโครที่ใช้งานได้ง่ายอาจถูกใช้ในลักษณะอื่นซึ่งจะทำให้เกิดข้อผิดพลาดในการคอมไพล์แปลก ๆ (เช่นQ_PROPERTY
ใช้กับโครงสร้างสมาชิก)
เนื่องจากความไม่แน่นอนทั้งหมดนี้จึงเป็นรูปแบบรหัสที่ดีในการหลีกเลี่ยงมาโครเหมือนกับที่คุณมักจะหลีกเลี่ยง gotos
constexpr
ถูกกำหนดตามความหมายและโดยทั่วไปจะสร้างปัญหาน้อยกว่ามาก
#if
เช่นสิ่งที่พรีโปรเซสเซอร์มีประโยชน์จริง การกำหนดค่าคงที่ไม่ได้เป็นหนึ่งในสิ่งที่พรีโพรเซสเซอร์จะเป็นประโยชน์สำหรับเว้นแต่ว่าคงต้องเป็นแมโครเพราะมันใช้ในสภาพ preprocessor #if
ใช้ ถ้าค่าคงที่สำหรับใช้ในโค้ด C ++ ปกติ (ไม่ใช่คำสั่งพรีโปรเซสเซอร์) ให้ใช้ตัวแปร C ++ ปกติไม่ใช่มาโครตัวประมวลผลล่วงหน้า
คำตอบที่ดีโดยJonathon Wakely ฉันขอแนะนำให้คุณดูคำตอบของ jogojapanว่าอะไรคือความแตกต่างระหว่างconst
และconstexpr
ก่อนที่คุณจะพิจารณาการใช้มาโคร
มาโครเป็นใบ้ แต่ในทางที่ดี อย่างเห็นได้ชัดในปัจจุบันพวกเขาเป็นตัวช่วยในการสร้างเมื่อคุณต้องการให้ส่วนที่เฉพาะเจาะจงมาก ๆ ของโค้ดของคุณถูกคอมไพล์เมื่อมีการ "กำหนดพารามิเตอร์การสร้าง" โดยปกติทุกที่หมายถึงคือการชื่อแมโครของคุณหรือดีกว่ายังขอเรียกมันTrigger
และสิ่งที่เพิ่มชอบ/D:Trigger
, -DTrigger
ฯลฯ เพื่อสร้างเครื่องมือที่ถูกนำมาใช้
แม้ว่าจะมีการใช้งานมาโครที่แตกต่างกันมากมาย แต่สิ่งเหล่านี้คือสองสิ่งที่ฉันเห็นบ่อยที่สุดว่าไม่ใช่แนวทางปฏิบัติที่ไม่ดี / ล้าสมัย:
ดังนั้นในขณะที่คุณสามารถบรรลุเป้าหมายเดียวกันในการกำหนด int ด้วยconstexpr
หรือ a MACRO
แต่ก็ไม่น่าจะเป็นไปได้ที่ทั้งสองจะทับซ้อนกันเมื่อใช้อนุสัญญาสมัยใหม่ นี่คือการใช้งานมาโครทั่วไปบางส่วนที่ยังไม่ได้ยุติลง
#if defined VERBOSE || defined DEBUG || defined MSG_ALL
// Verbose message-handling code here
#endif
เป็นอีกตัวอย่างหนึ่งสำหรับการใช้งานมาโครสมมติว่าคุณมีฮาร์ดแวร์ที่กำลังจะเปิดตัวหรืออาจเป็นรุ่นเฉพาะที่มีวิธีแก้ปัญหาที่ยุ่งยากบางอย่างที่คนอื่นไม่ต้องการ GEN_3_HW
เราจะกำหนดแมโครนี้เป็น
#if defined GEN_3_HW && defined _WIN64
// Windows-only special handling for 64-bit upcoming hardware
#elif defined GEN_3_HW && defined __APPLE__
// Special handling for macs on the new hardware
#elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__
// Greetings, Outlander! ;)
#else
// Generic handling
#endif