มาโครก็เหมือนกับเครื่องมืออื่น ๆ - ค้อนที่ใช้ในการฆาตกรรมไม่ใช่สิ่งชั่วร้ายเพราะเป็นค้อน มันเป็นความชั่วร้ายในวิธีที่บุคคลนั้นใช้มันในทางนั้น หากคุณต้องการตอกตะปูค้อนเป็นเครื่องมือที่สมบูรณ์แบบ
มีบางแง่มุมของมาโครที่ทำให้ "ไม่ดี" (ฉันจะขยายความในแต่ละด้านในภายหลังและแนะนำทางเลือกอื่น):
- คุณไม่สามารถแก้ไขข้อบกพร่องมาโคร
- การขยายมาโครอาจนำไปสู่ผลข้างเคียงที่แปลกประหลาด
- มาโครไม่มี "เนมสเปซ" ดังนั้นหากคุณมีมาโครที่ขัดแย้งกับชื่อที่ใช้ในที่อื่นคุณจะได้รับการแทนที่มาโครในที่ที่คุณไม่ต้องการซึ่งมักจะนำไปสู่ข้อความแสดงข้อผิดพลาดแปลก ๆ
- มาโครอาจส่งผลต่อสิ่งที่คุณไม่ทราบ
ขอขยายความเล็กน้อยที่นี่:
1) ไม่สามารถดีบักมาโครได้
เมื่อคุณมีมาโครที่แปลเป็นตัวเลขหรือสตริงซอร์สโค้ดจะมีชื่อมาโครและตัวดีบั๊กจำนวนมากคุณจะไม่สามารถ "ดู" ว่ามาโครแปลเป็นอะไรได้ คุณจึงไม่รู้ว่าเกิดอะไรขึ้น
เปลี่ยน : ใช้enum
หรือconst T
สำหรับมาโคร "ฟังก์ชันเหมือน" เนื่องจากตัวดีบักทำงานในระดับ "ต่อบรรทัดต้นทางที่คุณอยู่" มาโครของคุณจะทำหน้าที่เหมือนคำสั่งเดียวไม่ว่าจะเป็นคำสั่งเดียวหรือเป็นร้อย ทำให้ยากที่จะเข้าใจว่าเกิดอะไรขึ้น
การเปลี่ยน : ใช้ฟังก์ชัน - อินไลน์หากจำเป็นต้อง "เร็ว" (แต่ระวังว่าอินไลน์มากเกินไปไม่ใช่เรื่องดี)
2) การขยายมาโครอาจมีผลข้างเคียงแปลก ๆ
ที่มีชื่อเสียงเป็นที่หนึ่งและการใช้#define SQUARE(x) ((x) * (x))
x2 = SQUARE(x++)
ซึ่งนำไปสู่x2 = (x++) * (x++);
ซึ่งแม้ว่าจะเป็นรหัสที่ถูกต้อง [1] ก็แทบจะไม่ใช่สิ่งที่โปรแกรมเมอร์ต้องการอย่างแน่นอน ถ้าเป็นฟังก์ชั่นมันจะดีที่จะทำ x ++ และ x จะเพิ่มขึ้นเพียงครั้งเดียว
อีกตัวอย่างหนึ่งคือ "if else" ในมาโครสมมติว่าเรามีสิ่งนี้:
#define safe_divide(res, x, y) if (y != 0) res = x/y;
แล้ว
if (something) safe_divide(b, a, x);
else printf("Something is not set...");
มันกลายเป็นสิ่งที่ผิดไปโดยสิ้นเชิง ....
เปลี่ยน : ฟังก์ชั่นจริง
3) มาโครไม่มีเนมสเปซ
หากเรามีมาโคร:
#define begin() x = 0
และเรามีโค้ดบางอย่างใน C ++ ที่ใช้เริ่มต้น:
std::vector<int> v;
... stuff is loaded into v ...
for (std::vector<int>::iterator it = myvector.begin() ; it != myvector.end(); ++it)
std::cout << ' ' << *it;
ตอนนี้คุณคิดว่าคุณได้รับข้อความแสดงข้อผิดพลาดอะไรและคุณมองหาข้อผิดพลาดจากที่ใด [สมมติว่าคุณลืมไปแล้วหรือไม่รู้ด้วยซ้ำว่ามาโครเริ่มต้นที่อยู่ในไฟล์ส่วนหัวบางไฟล์ที่คนอื่นเขียน [และจะสนุกยิ่งขึ้นหากคุณรวมมาโครนั้นไว้ก่อนการรวม - คุณจะจมดิ่งอยู่กับข้อผิดพลาดแปลก ๆ ที่ไม่สมเหตุสมผลเมื่อคุณดูโค้ดนั้นเอง
การแทนที่ : ไม่มีการแทนที่เป็น "กฎ" มากนัก - ใช้เฉพาะชื่อตัวพิมพ์ใหญ่สำหรับมาโครและห้ามใช้ชื่อตัวพิมพ์ใหญ่ทั้งหมดสำหรับสิ่งอื่น
4) มาโครมีเอฟเฟกต์ที่คุณไม่รู้
ใช้ฟังก์ชันนี้:
#define begin() x = 0
#define end() x = 17
... a few thousand lines of stuff here ...
void dostuff()
{
int x = 7;
begin();
... more code using x ...
printf("x=%d\n", x);
end();
}
ตอนนี้โดยไม่ต้องดูมาโครคุณจะคิดว่าการเริ่มต้นเป็นฟังก์ชันซึ่งไม่ควรส่งผลกระทบต่อ x
สิ่งนี้และฉันเคยเห็นตัวอย่างที่ซับซ้อนกว่านี้มากอาจทำให้วันของคุณยุ่งเหยิง!
การแทนที่ : อย่าใช้มาโครเพื่อตั้งค่า x หรือส่ง x เข้าเป็นอาร์กิวเมนต์
มีหลายครั้งที่การใช้มาโครเป็นประโยชน์อย่างแน่นอน ตัวอย่างหนึ่งคือการรวมฟังก์ชันด้วยมาโครเพื่อส่งต่อข้อมูลไฟล์ / บรรทัด:
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x) my_debug_free(x, __FILE__, __LINE__)
ตอนนี้เราสามารถใช้my_debug_malloc
เป็น malloc ปกติในโค้ดได้ แต่มันมีอาร์กิวเมนต์พิเศษดังนั้นเมื่อมันมาถึงจุดสิ้นสุดและเราสแกน "องค์ประกอบหน่วยความจำใดที่ไม่ได้รับการปลดปล่อย" เราสามารถพิมพ์ได้ว่าจะทำการจัดสรรที่ใดเพื่อให้ โปรแกรมเมอร์สามารถติดตามการรั่วไหล
[1] เป็นพฤติกรรมที่ไม่ได้กำหนดไว้ในการอัปเดตหนึ่งตัวแปรมากกว่าหนึ่งครั้ง "ในจุดลำดับ" จุดลำดับไม่เหมือนกับคำสั่ง แต่สำหรับเจตนาและวัตถุประสงค์ส่วนใหญ่นั่นคือสิ่งที่เราควรพิจารณาว่าเป็น การทำเช่นx++ * x++
นั้นจะอัปเดตx
สองครั้งซึ่งไม่ได้กำหนดไว้และอาจนำไปสู่ค่าที่แตกต่างกันในระบบx
ต่างๆ
#pragma
ไม่ใช่มาโคร