typedefs และ #defines


32

เราทุกคนเคยใช้typedefและ#defineครั้งเดียวแน่นอน วันนี้ในขณะที่ทำงานกับพวกเขาฉันเริ่มไตร่ตรองในบางสิ่ง

พิจารณาสถานการณ์ด้านล่าง 2 สถานการณ์เพื่อใช้intประเภทข้อมูลที่มีชื่ออื่น:

typedef int MYINTEGER

และ

#define MYINTEGER int

เช่นเดียวกับสถานการณ์ข้างต้นเราสามารถทำสิ่งต่าง ๆ ได้โดยใช้ #define และทำได้เช่นเดียวกันโดยใช้ typedef แม้ว่าวิธีการที่เราทำแบบเดียวกันอาจแตกต่างกันมาก #define ยังสามารถทำการกระทำของ MACRO ซึ่ง typedef ไม่สามารถทำได้

แม้ว่าเหตุผลพื้นฐานสำหรับการใช้งานนั้นแตกต่างกัน แต่การทำงานต่างกันอย่างไร เมื่อใดที่ควรจะเป็นที่ต้องการมากกว่ากันเมื่อทั้งสองสามารถใช้? อีกอย่างหนึ่งรับประกันได้ว่าจะเร็วกว่าสถานการณ์อื่นหรือไม่ (เช่น #define เป็นคำสั่งของตัวประมวลผลล่วงหน้าดังนั้นทุกอย่างจะทำเร็วกว่าการคอมไพล์หรือรันไทม์)


8
ใช้ #define สิ่งที่ออกแบบมา การรวบรวมแบบมีเงื่อนไขขึ้นอยู่กับคุณสมบัติที่คุณไม่สามารถรู้ได้เมื่อเขียนรหัส (เช่น OS / Compiler) ใช้โครงสร้างภาษาอย่างอื่น
Martin York

คำตอบ:


67

typedefโดยทั่วไปA เป็นที่ต้องการเว้นแต่ว่ามีเหตุผลแปลก ๆ ที่คุณต้องการแมโครโดยเฉพาะ

มาโครทำการทดแทนข้อความซึ่งสามารถทำรุนแรงกับความหมายของรหัส ตัวอย่างเช่นกำหนด:

#define MYINTEGER int

คุณสามารถเขียนตามกฎหมาย:

short MYINTEGER x = 42;

เพราะการขยายตัวไปshort MYINTEGERshort int

ในทางกลับกันด้วย typedef:

typedef int MYINTEGER:

ชื่อMYINTEGERเป็นชื่ออื่นสำหรับประเภท intไม่ใช่การทดแทนข้อความสำหรับคำหลัก "int"

สิ่งต่าง ๆ แย่ลงด้วยประเภทที่ซับซ้อนกว่า ตัวอย่างเช่นให้สิ่งนี้:

typedef char *char_ptr;
char_ptr a, b;
#define CHAR_PTR char*
CHAR_PTR c, d;

a,, bและcเป็นตัวชี้ทั้งหมด แต่dเป็น a charเนื่องจากบรรทัดสุดท้ายขยายเป็น:

char* c, d;

ซึ่งเทียบเท่ากับ

char *c;
char d;

(Typedefs สำหรับประเภทของตัวชี้มักจะไม่ใช่ความคิดที่ดี แต่นี่แสดงให้เห็นถึงจุด)

อีกกรณีที่แปลก:

#define DWORD long
DWORD double x;     /* Huh? */

1
ตัวชี้จะตำหนิอีกครั้ง! :) ตัวอย่างอื่นนอกเหนือจากตัวชี้ที่ไม่ฉลาดในการใช้มาโครดังกล่าวหรือไม่
c0da

2
ไม่ว่าฉันเคยใช้แมโครในสถานที่ของแต่วิธีการที่เหมาะสมในการสร้างแมโครที่ทำอะไรบางอย่างกับสิ่งต่อไปนี้คือการใช้อาร์กิวเมนต์:typedef #define CHAR_PTR(x) char *xอย่างน้อยก็จะทำให้คอมไพเลอร์เป็น kvetch หากใช้ผิด
Blrfl

1
อย่าลืม:const CHAR_PTR mutable_pointer_to_const_char; const char_ptr const_pointer_to_mutable_char;
Jon Purdy

3
กำหนดยังทำให้ข้อความแสดงข้อผิดพลาดเข้าใจไม่ได้
Thomas Bonini

ตัวอย่างของความเจ็บปวดที่ใช้มาโครอย่างผิด ๆ อาจทำให้เกิด (แม้ว่าจะไม่เกี่ยวข้องโดยตรงกับมาโครกับ typedefs): github.com/Keith-S-Thompson/42
Keith Thompson

15

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


แต่ตามที่ฉันได้แสดงในตัวอย่างที่มีให้ในคำถาม typedef และ #define ใช้จำนวนคำเท่ากัน! อีกทั้งคำ 3 คำของมาโครจะไม่ทำให้เกิดความสับสนเนื่องจากคำเหมือนกัน แต่เพิ่งจัดเรียงใหม่ :)
c0da

2
ใช่ แต่ไม่ใช่เรื่องย่อ แมโครสามารถทำ zillion อย่างอื่นนอกเหนือจากการพิมพ์ดีด แต่โดยส่วนตัวฉันคิดว่าการกำหนดขอบเขตเป็นสิ่งสำคัญที่สุด
Tamás Szelei

2
@ c0da - คุณไม่ควรใช้มาโครสำหรับ typedefs คุณควรใช้ typedefs สำหรับ typedefs ผลกระทบที่แท้จริงของมาโครนั้นอาจแตกต่างกันมาก
Joris Timmermans

15

ซอร์สโค้ดส่วนใหญ่เขียนขึ้นสำหรับนักพัฒนาซอฟต์แวร์เพื่อน คอมพิวเตอร์ใช้รุ่นที่คอมไพล์แล้ว

ในมุมมองนี้typedefมีความหมายที่#defineไม่ได้


6

คำตอบของ Keith Thompsonนั้นดีมากและเมื่อรวมกับคะแนนเพิ่มเติมของTamás Szeleiเกี่ยวกับการกำหนดขอบเขตควรให้พื้นหลังทั้งหมดที่คุณต้องการ

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


5

นอกจากคำพูดที่ถูกต้องทั้งหมดเกี่ยวกับขอบเขตและการแทนที่ข้อความที่กำหนดไว้แล้ว #define ยังไม่สามารถทำงานร่วมกับ typedef ได้อย่างสมบูรณ์! คือเมื่อมันมาถึงกรณีของตัวชี้ฟังก์ชั่น

typedef void(*fptr_t)(void);

นี่เป็นการประกาศชนิดfptr_tซึ่งเป็นตัวชี้ไปยังฟังก์ชันของชนิดvoid func(void)นั้น

คุณไม่สามารถประกาศประเภทนี้ด้วยแมโคร #define fptr_t void(*)(void)เห็นได้ชัดว่าจะไม่ทำงาน คุณต้องเขียนอะไรบางอย่างที่คลุมเครือเช่น#define fptr_t(name) void(*name)(void)นั้นซึ่งไม่สมเหตุสมผลในภาษา C ซึ่งเราไม่มีคอนสตรัคเตอร์

ตัวชี้ Array ไม่สามารถประกาศด้วย #define ได้: typedef int(*arr_ptr)[10];


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


1
ยิ่งไปกว่านั้นคือการรวมกันของฟังก์ชั่นและพอยน์เตอร์พอยน์เตอร์เช่นchar *(*(*foo())[])();( fooเป็นฟังก์ชันที่ส่งคืนพอยน์เตอร์ไปยังอาเรย์ของพอยcharน์เตอร์
John Bode

4

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


2

นอกเหนือจากข้อโต้แย้งทั่วไปกับ define คุณจะเขียนฟังก์ชันนี้โดยใช้ Macros อย่างไร

template <typename IterType>
typename IterType::value_type Sum(
    const IterType& begin, 
    const IterType& end, 
    const IterType::value_type& initialValue)
{
    typename IterType::value_type result = initialValue;
    for (IterType i = begin; i != end; ++i)
        result += i;

    return result;
}

....

vector<int> values;
int sum = Sum(values.begin(), values.end(), 0);

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

* ฉันเพิ่งเขียนสิ่งนี้ที่นี่ฉันรวบรวมมันไว้เป็นเครื่องออกกำลังกายสำหรับผู้อ่าน :-)

แก้ไข:

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

template <typename ValueType, typename AllocatorType>
class vector
{
public:
    typedef ValueType value_type;
...
}

การใช้ typedefs ภายในคอนเทนเนอร์มาตรฐานอนุญาตให้มีฟังก์ชั่นทั่วไป (เช่นที่ฉันสร้างไว้ด้านบน) เพื่ออ้างอิงประเภทเหล่านี้ ฟังก์ชั่น "ผลรวม" จะถูก templated ในประเภทของภาชนะ ( std::vector<int>) ไม่ได้อยู่ในประเภทที่ถืออยู่ภายในภาชนะ ( int) หากไม่มี typedef จะไม่สามารถอ้างถึงประเภทภายในนั้นได้

ดังนั้น typedefs จึงเป็นศูนย์กลางของ Modern C ++ และไม่สามารถทำได้กับ Macros


คำถามที่ถามเกี่ยวกับมาโคร vs typedefไม่ใช่มาโครเทียบกับฟังก์ชั่นอินไลน์
Ben Voigt

แน่นอนและนี่เป็นไปได้เฉพาะกับ typedefs ... นั่นคือสิ่งที่ value_type
Chris Pitman

นี่ไม่ใช่ typedef แต่เป็นเทมเพลตการเขียนโปรแกรม ฟังก์ชั่นหรือฟังก์ชั่นไม่ได้เป็นประเภทไม่ว่าจะเป็นทั่วไป

@Lundin ฉันได้เพิ่มรายละเอียดเพิ่มเติม: ฟังก์ชั่น Sum เป็นไปได้เพียงเพราะคอนเทนเนอร์มาตรฐานใช้ typedefs เพื่อแสดงประเภทที่ templated ฉันไม่ได้พูดว่า Sum เป็น typedef ฉันกำลังพูดstd :: vector <T> :: value_typeเป็น typedef อย่าลังเลที่จะดูแหล่งที่มาของการใช้งานไลบรารีมาตรฐานใด ๆ เพื่อตรวจสอบ
Chris Pitman

ยุติธรรมพอสมควร บางทีอาจเป็นการดีกว่าที่จะโพสต์ตัวอย่างที่สองเท่านั้น

1

typedefเหมาะกับปรัชญา C ++: การตรวจสอบ / ยืนยันที่เป็นไปได้ทั้งหมดในเวลารวบรวม #defineเป็นเพียงเคล็ดลับพรีโปรเซสเซอร์ซึ่งซ่อนความหมายจำนวนมากไปยังคอมไพเลอร์ คุณไม่ควรกังวลเกี่ยวกับประสิทธิภาพการรวบรวมมากกว่าความถูกต้องของรหัส

เมื่อคุณสร้างประเภทใหม่คุณจะกำหนด "สิ่ง" ใหม่ที่โดเมนของโปรแกรมของคุณใช้งาน ดังนั้นคุณสามารถใช้ "สิ่ง" นี้เพื่อเขียนฟังก์ชั่นและคลาสและคุณสามารถใช้คอมไพเลอร์เพื่อประโยชน์ของคุณในการตรวจสอบแบบสแตติก อย่างไรก็ตามเนื่องจาก C ++ เข้ากันได้กับ C จึงมีการแปลงโดยนัยจำนวนมากระหว่างintและประเภทที่มีพื้นฐานซึ่งไม่ได้สร้างคำเตือน ดังนั้นคุณจะไม่ได้รับเช็คเต็มรูปแบบในกรณีนี้ อย่างไรก็ตามคุณสามารถแทนที่typedefด้วยenumเพื่อค้นหาการแปลงโดยนัย เช่นหากคุณได้typedef int Age;แล้วคุณสามารถแทนที่ด้วยenum Age { };และคุณจะได้รับชนิดของข้อผิดพลาดทั้งหมดโดยการแปลงนัยระหว่างและAgeint

อีกสิ่งหนึ่งคือสามารถภายในtypedefnamespace


1

การใช้การนิยามแทน typedefs ทำให้เกิดปัญหาภายใต้แง่มุมที่สำคัญอีกประการหนึ่งและนั่นคือแนวคิดของลักษณะประเภท พิจารณาคลาสที่แตกต่างกัน (ลองนึกถึงคอนเทนเนอร์มาตรฐาน) ซึ่งทั้งหมดนี้กำหนดประเภทเฉพาะของพวกเขา คุณสามารถเขียนโค้ดทั่วไปโดยอ้างอิงจาก typedefs ตัวอย่างของสิ่งนี้รวมถึงข้อกำหนดของภาชนะทั่วไป (มาตรฐาน c ++ 23.2.1 [container.requirements.general]) เช่น

X::value_type
X::reference
X::difference_type
X::size_type

ทั้งหมดนี้ไม่สามารถแสดงได้ในลักษณะทั่วไปที่มีมาโครเนื่องจากไม่ได้กำหนดขอบเขต


1

อย่าลืมความหมายของสิ่งนี้ในดีบักเกอร์ของคุณ ผู้ดีบักบางรายไม่สามารถจัดการ #define ได้ดี เล่นรอบกับทั้งในดีบักเกอร์ที่คุณใช้ จำไว้ว่าคุณจะใช้เวลาในการอ่านมากกว่าการเขียน


-2

"#define" จะแทนที่สิ่งที่คุณเขียนหลังจากนั้น typedef จะสร้างประเภท ดังนั้นหากคุณต้องการมีประเภท custome - ใช้ typedef หากคุณต้องการแมโคร - ใช้ define


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