มาโคร C ++ จะมีประโยชน์เมื่อใด [ปิด]


177

C preprocessor กลัวแก้ตัวและรังเกียจโดยชุมชน c ++ ในเรียงรายฟังก์ชั่น consts #defineและแม่มักจะปลอดภัยและมีทางเลือกที่ดีกว่ากับ

แมโครต่อไปนี้:

#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)  

ไม่มีทางเหนือกว่าประเภทที่ปลอดภัย:

inline bool succeeded(int hr) { return hr >= 0; }

แต่มาโครมีที่อยู่โปรดระบุการใช้งานที่คุณพบสำหรับมาโครที่คุณไม่สามารถทำได้หากไม่มีตัวประมวลผลล่วงหน้า

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


ฉันเคยใช้แอปพลิเคชั่น C ++ ที่เต็มไปด้วยมาโครที่ใช้เวลา 45 นาทีในการสร้างแทนที่มาโครด้วยฟังก์ชั่นแบบอินไลน์และทำให้การสร้างลดลงเหลือน้อยกว่า 15 นาที
endian


เธรดเป็นเรื่องเกี่ยวกับบริบทที่มาโครเป็นประโยชน์ไม่ใช่บริบทที่เป็นประโยชน์
underscore_d

คำตอบ:


123

ในฐานะที่เป็นห่อสำหรับฟังก์ชั่นการแก้ปัญหาที่จะผ่านสิ่งที่ต้องการโดยอัตโนมัติ__FILE__, __LINE__ฯลฯ :

#ifdef ( DEBUG )
#define M_DebugLog( msg )  std::cout << __FILE__ << ":" << __LINE__ << ": " << msg
#else
#define M_DebugLog( msg )
#endif

14
จริงๆแล้วข้อมูลโค้ดดั้งเดิม: << FILE ":" << ใช้ได้ดีไฟล์สร้างค่าคงที่ของสตริงซึ่งจะถูกต่อกับ ":" ลงในสตริงเดี่ยวโดยตัวประมวลผลล่วงหน้า
Frank Szczerba

12
สิ่งนี้ต้องการเพียงตัวประมวลผลล่วงหน้าเพราะ__FILE__และ__LINE__ ยังต้องการตัวประมวลผลล่วงหน้าด้วย การใช้มันในโค้ดของคุณเปรียบเสมือนเวกเตอร์ติดเชื้อสำหรับตัวประมวลผลล่วงหน้า
TED

93

วิธีการจะต้องสมบูรณ์รหัสที่คอมไพล์ได้เสมอ มาโครอาจเป็นรหัสเศษเล็กเศษน้อย ดังนั้นคุณสามารถกำหนดแมโคร foreach:

#define foreach(list, index) for(index = 0; index < list.size(); index++)

และใช้เป็นดังนี้:

foreach(cookies, i)
    printf("Cookie: %s", cookies[i]);

ตั้งแต่ C ++ 11 นี้ถูกแทนที่โดยช่วงที่ใช้สำหรับวง


6
+1 หากคุณใช้ไวยากรณ์ตัววนซ้ำที่ซับซ้อนอย่างน่าขันการเขียนแมโครสไตล์ foreach สามารถทำให้อ่านและบำรุงรักษาโค้ดของคุณได้ง่ายขึ้น ฉันทำไปแล้วมันได้ผล
postfuturist

9
ความคิดเห็นส่วนใหญ่ไม่เกี่ยวข้องอย่างสมบูรณ์จนถึงจุดที่มาโครอาจเป็นส่วนย่อยของรหัสแทนที่จะเป็นรหัสสมบูรณ์ แต่ขอบคุณสำหรับ nitpicking
jdmichal

12
นี่คือ C ไม่ใช่ C ++ หากคุณกำลังใช้ C ++ คุณควรใช้ตัววนซ้ำและ std :: for_each
chrish

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

8
และมันก็เป็นที่น่าสังเกตว่า BOOST_FOREACH นั้นเป็นตัวของตัวเอง (แต่เป็นคนที่มีความคิดดีมาก)
Tyler McHenry

59

ตัวปกป้องไฟล์ส่วนหัวจำเป็นต้องมีมาโคร

มีพื้นที่อื่น ๆ ที่จำเป็นต้องมีมาโครหรือไม่ ไม่มาก (ถ้ามี)

มีสถานการณ์อื่น ๆ ที่ได้รับประโยชน์จากมาโครหรือไม่ ใช่!!!

ที่เดียวที่ฉันใช้มาโครคือรหัสซ้ำ ๆ ตัวอย่างเช่นเมื่อห่อโค้ด C ++ เพื่อใช้กับอินเทอร์เฟซอื่น (. NET, COM, Python, ฯลฯ ... ) ฉันต้องจับข้อยกเว้นประเภทต่างๆ นี่คือวิธีที่ฉัน:

#define HANDLE_EXCEPTIONS \
catch (::mylib::exception& e) { \
    throw gcnew MyDotNetLib::Exception(e); \
} \
catch (::std::exception& e) { \
    throw gcnew MyDotNetLib::Exception(e, __LINE__, __FILE__); \
} \
catch (...) { \
    throw gcnew MyDotNetLib::UnknownException(__LINE__, __FILE__); \
}

ฉันต้องใส่ตัวยึดเหล่านี้ในทุกฟังก์ชั่นเสื้อคลุม แทนที่จะพิมพ์บล็อกการจับแบบเต็มทุกครั้งฉันแค่พิมพ์:

void Foo()
{
    try {
        ::mylib::Foo()
    }
    HANDLE_EXCEPTIONS
}

ทำให้การบำรุงรักษาง่ายขึ้น หากฉันต้องเพิ่มประเภทยกเว้นใหม่มีเพียงที่เดียวเท่านั้นที่ฉันต้องเพิ่ม

มีตัวอย่างที่เป็นประโยชน์อื่น ๆ ด้วยเช่นกันซึ่งส่วนใหญ่ประกอบด้วยแมโคร__FILE__และ__LINE__ตัวประมวลผลล่วงหน้า

อย่างไรก็ตามมาโครมีประโยชน์มากเมื่อใช้อย่างถูกต้อง มาโครไม่ได้ชั่วร้าย - การใช้ในทางที่ผิดคือความชั่วร้าย


7
คอมไพเลอร์ส่วนใหญ่ให้การสนับสนุนใน#pragma onceวันนี้ดังนั้นฉันจึงสงสัยว่าผู้พิทักษ์จำเป็นจริงๆ
1800 ข้อมูล

13
พวกเขาคือถ้าคุณเขียนคอมไพเลอร์ทั้งหมดแทนที่จะเป็นเพียง ;-) มากที่สุด
Steve Jessop

30
ดังนั้นแทนที่จะใช้ฟังก์ชั่น preprocessor มาตรฐานแบบพกพาคุณแนะนำให้ใช้ส่วนขยาย preprocessor เพื่อหลีกเลี่ยงการใช้ preprocessor หรือไม่ ดูเหมือนว่าไร้สาระกับฉัน
Logan Capaldo

#pragma onceแบ่งระบบสร้างทั่วไปมากมาย
Miles Rout

4
void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }มีวิธีแก้ปัญหาสำหรับว่าที่ไม่ต้องใช้แมโครคือ: และในด้านฟังก์ชั่น:void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
MikeMB

51

ส่วนใหญ่:

  1. รวมถึงยาม
  2. การรวบรวมตามเงื่อนไข
  3. การรายงาน (มาโครที่กำหนดไว้ล่วงหน้าเช่น__LINE__และ__FILE__)
  4. (ไม่ค่อย) การทำซ้ำรูปแบบรหัสซ้ำ
  5. ในรหัสของคู่แข่งของคุณ

กำลังมองหาความช่วยเหลือเกี่ยวกับวิธีการรับหมายเลข 5 คุณช่วยแนะนำทางแก้ปัญหาให้ฉันได้ไหม?
สูงสุด

50

การรวบรวมตามเงื่อนไขภายในเพื่อเอาชนะปัญหาความแตกต่างระหว่างคอมไพเลอร์:

#ifdef ARE_WE_ON_WIN32
#define close(parm1)            _close (parm1)
#define rmdir(parm1)            _rmdir (parm1)
#define mkdir(parm1, parm2)     _mkdir (parm1)
#define access(parm1, parm2)    _access(parm1, parm2)
#define create(parm1, parm2)    _creat (parm1, parm2)
#define unlink(parm1)           _unlink(parm1)
#endif

12
ใน C ++ คุณสามารถรับฟังก์ชั่นอินไลน์ได้เหมือนกัน: <code> #ifdef ARE_WE_ON_WIN32 <br> อินไลน์ int close (int i) {return _close (i); } <br> #endif </code>
paercebal

2
ที่ลบ # define แต่ไม่ใช่ #ifdef และ #endif อย่างไรก็ตามฉันเห็นด้วยกับคุณ
Gorpik

19
ไม่เคยกำหนดมาโครตัวพิมพ์เล็ก มาโครในการเปลี่ยนฟังก์ชั่นเป็นฝันร้ายของฉัน (ขอบคุณ Microsoft) ตัวอย่างที่ดีที่สุดอยู่ในบรรทัดแรก ห้องสมุดหลายแห่งมีcloseหน้าที่หรือวิธีการ จากนั้นเมื่อคุณรวมส่วนหัวของไลบรารีนี้และส่วนหัวที่มีแมโครนี้มากกว่าที่คุณมีปัญหาใหญ่คุณจะไม่สามารถใช้ไลบรารี API
Marek R

AndrewStein คุณเห็นประโยชน์ใด ๆ ของการใช้มาโครในบริบทนี้เหนือข้อเสนอแนะของ @ paercebal หรือไม่? ถ้าไม่เป็นเช่นนั้นมาโครก็ฟรี
einpoklum

1
#ifdef WE_ARE_ON_WIN32ได้โปรด :)
การแข่งขัน Lightness ใน Orbit

38

เมื่อคุณต้องการสร้างสตริงจากนิพจน์ตัวอย่างที่ดีที่สุดสำหรับสิ่งนี้คือassert( #xเปลี่ยนค่าxเป็นสตริง)

#define ASSERT_THROW(condition) \
if (!(condition)) \
     throw std::exception(#condition " is false");

5
แค่ nitpick แต่โดยส่วนตัวแล้วฉันจะปล่อยอัฒภาคออกเป็นการส่วนตัว
Michael Myers

10
ฉันเห็นด้วยที่จริงแล้วฉันจะใส่มันใน {} ในขณะที่ (เป็นเท็จ) (เพื่อป้องกันการไฮแจ็คกิ้งอื่น ๆ ) แต่ฉันต้องการทำให้มันง่าย
Motti

33

const char *ค่าคงที่สตริงบางครั้งจะดีกว่าที่กำหนดไว้เป็นแมโครเนื่องจากคุณสามารถทำมากขึ้นด้วยตัวอักษรของสตริงกว่าด้วย

ตัวอักษรเช่น String สามารถตัดแบ่งได้อย่างง่ายดาย

#define BASE_HKEY "Software\\Microsoft\\Internet Explorer\\"
// Now we can concat with other literals
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "Settings", &settings);
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "TypedURLs", &URLs);

หากมีการconst char *ใช้งานแล้วคลาสสตริงบางประเภทจะต้องใช้ในการดำเนินการต่อข้อมูลที่รันไทม์:

const char* BaseHkey = "Software\\Microsoft\\Internet Explorer\\";
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "Settings").c_str(), &settings);
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "TypedURLs").c_str(), &URLs);

2
ใน C ++ 11 ฉันจะถือว่านี่เป็นส่วนที่สำคัญที่สุด (นอกเหนือจากที่มีเจ้าหน้าที่รักษาความปลอดภัย) มาโครเป็นสิ่งที่ดีที่สุดที่เรามีสำหรับการประมวลผลสตริงเวลาคอมไพล์ นี่เป็นคุณลักษณะที่ฉันหวังว่าเราจะได้รับใน C ++ 11 ++
David Stone

1
นี่คือสถานการณ์ที่ทำให้ฉันต้องการมาโครใน C #
Rawling

2
ฉันหวังว่าฉันจะสามารถ +42 นี้ สิ่งที่สำคัญมากแม้ว่าจะไม่ได้จดจำบ่อยครั้งเกี่ยวกับตัวอักษรสตริง
Daniel Kamil Kozar

24

เมื่อคุณต้องการที่จะเปลี่ยนการไหลของโปรแกรม ( return, breakและcontinue) รหัสในพฤติกรรมการทำงานที่แตกต่างกว่ารหัสที่ inlined จริงในการทำงาน

#define ASSERT_RETURN(condition, ret_val) \
if (!(condition)) { \
    assert(false && #condition); \
    return ret_val; }

// should really be in a do { } while(false) but that's another discussion.

การขว้างข้อยกเว้นดูเหมือนจะเป็นทางเลือกที่ดีกว่า
einpoklum

เมื่อเขียนหลาม C (++) ส่วนขยายข้อยกเว้นจะแพร่กระจายโดยการตั้งค่าสตริงยกเว้นจากนั้นกลับมาหรือ-1 NULLดังนั้นมาโครสามารถลดรหัสสำเร็จรูปได้อย่างมาก
black_puppydog


17

คุณไม่สามารถทำการโต้แย้งการเรียกใช้ฟังก์ชันโดยใช้การเรียกฟังก์ชันปกติ ตัวอย่างเช่น:

#define andm(a, b) (a) && (b)

bool andf(bool a, bool b) { return a && b; }

andm(x, y) // short circuits the operator so if x is false, y would not be evaluated
andf(x, y) // y will always be evaluated

3
อาจจะเป็นประเด็นที่กว้างกว่านี้: ฟังก์ชั่นประเมินข้อโต้แย้งของพวกเขาเพียงครั้งเดียว มาโครสามารถประเมินอาร์กิวเมนต์ได้มากขึ้นหรือน้อยลง
Steve Jessop

@ [Greg Rogers] มาโครตัวประมวลผลล่วงหน้าทั้งหมดทำหน้าที่แทนข้อความ เมื่อคุณเข้าใจแล้วไม่ควรมีเรื่องลึกลับอีกต่อไป
1800 ข้อมูล

คุณสามารถรับพฤติกรรมที่เท่าเทียมกันโดย templatizing andf แทนที่จะบังคับให้การประเมินผลเป็น bool ก่อนที่จะเรียกใช้ฟังก์ชัน ฉันจะไม่ได้ตระหนักถึงสิ่งที่คุณพูดว่าเป็นจริงโดยไม่ต้องลองด้วยตัวเอง น่าสนใจ
Greg Rogers

คุณสามารถทำสิ่งนั้นกับแม่แบบได้อย่างไร?
1800 ข้อมูล

6
การซ่อนการดำเนินการลัดวงจรไว้เบื้องหลังแมโครสไตล์ฟังก์ชันเป็นหนึ่งในสิ่งที่ฉันไม่ต้องการเห็นในรหัสการผลิต
MikeMB

17

สมมติว่าเราจะเพิกเฉยต่อสิ่งที่ชัดเจนเช่นการ์ดส่วนหัว

บางครั้งคุณต้องการสร้างรหัสที่ต้องคัดลอก / วางโดย precompiler:

#define RAISE_ERROR_STL(p_strMessage)                                          \
do                                                                             \
{                                                                              \
   try                                                                         \
   {                                                                           \
      std::tstringstream strBuffer ;                                           \
      strBuffer << p_strMessage ;                                              \
      strMessage = strBuffer.str() ;                                           \
      raiseSomeAlert(__FILE__, __FUNCSIG__, __LINE__, strBuffer.str().c_str()) \
   }                                                                           \
   catch(...){}                                                                \
   {                                                                           \
   }                                                                           \
}                                                                              \
while(false)

ซึ่งช่วยให้คุณสามารถรหัสนี้:

RAISE_ERROR_STL("Hello... The following values " << i << " and " << j << " are wrong") ;

และสามารถสร้างข้อความเช่น:

Error Raised:
====================================
File : MyFile.cpp, line 225
Function : MyFunction(int, double)
Message : "Hello... The following values 23 and 12 are wrong"

โปรดทราบว่าเทมเพลตการผสมกับมาโครสามารถนำไปสู่ผลลัพธ์ที่ดียิ่งขึ้น (เช่นการสร้างค่าแบบเคียงข้างกันพร้อมชื่อตัวแปร)

ในบางครั้งคุณต้องใช้ __FILE__ และ / หรือ __LINE__ ของรหัสบางอย่างเพื่อสร้างข้อมูลการดีบักตัวอย่างเช่น ต่อไปนี้เป็นคลาสสิกสำหรับ Visual C ++:

#define WRNG_PRIVATE_STR2(z) #z
#define WRNG_PRIVATE_STR1(x) WRNG_PRIVATE_STR2(x)
#define WRNG __FILE__ "("WRNG_PRIVATE_STR1(__LINE__)") : ------------ : "

เช่นเดียวกับรหัสต่อไปนี้:

#pragma message(WRNG "Hello World")

มันสร้างข้อความเช่น:

C:\my_project\my_cpp_file.cpp (225) : ------------ Hello World

ในบางครั้งคุณต้องสร้างรหัสโดยใช้ตัวดำเนินการต่อข้อมูล # และ ## เช่นสร้าง getters และ setters สำหรับคุณสมบัติ (นี่เป็นกรณีที่ค่อนข้าง จำกัด ผ่าน)

ในบางครั้งคุณจะสร้างโค้ดมากกว่าจะไม่คอมไพล์หากใช้ผ่านฟังก์ชันเช่น:

#define MY_TRY      try{
#define MY_CATCH    } catch(...) {
#define MY_END_TRY  }

ซึ่งสามารถใช้เป็น

MY_TRY
   doSomethingDangerous() ;
MY_CATCH
   tryToRecoverEvenWithoutMeaningfullInfo() ;
   damnThoseMacros() ;
MY_END_TRY

(ยังฉันเห็นรหัสชนิดนี้ถูกใช้ครั้งเดียวเท่านั้น )

สุดท้าย แต่ไม่ท้ายสุดboost::foreach!!!

#include <string>
#include <iostream>
#include <boost/foreach.hpp>

int main()
{
    std::string hello( "Hello, world!" );

    BOOST_FOREACH( char ch, hello )
    {
        std::cout << ch;
    }

    return 0;
}

(หมายเหตุ: การคัดลอกโค้ด / วางจากหน้าแรกของ Boost)

ซึ่งเป็น (IMHO) std::for_eachวิธีที่ดีกว่า

ดังนั้นแมโครจึงมีประโยชน์เสมอเพราะอยู่นอกกฎคอมไพเลอร์ปกติ แต่ฉันพบว่าเวลาส่วนใหญ่ที่ฉันเห็นพวกเขายังคงอยู่ในรหัส C อย่างมีประสิทธิภาพไม่เคยแปลเป็นภาษา C ++ ที่เหมาะสม


1
ใช้ CPP สำหรับสิ่งที่คอมไพเลอร์ไม่สามารถทำได้ ตัวอย่างเช่น RAISE_ERROR_STL ควรใช้ CPP เพื่อกำหนดไฟล์บรรทัดและลายเซ็นฟังก์ชันเท่านั้นและส่งต่อไปยังฟังก์ชัน (อาจเป็นแบบอินไลน์) ที่เหลือ
Rainer Blome

โปรดอัปเดตคำตอบของคุณเพื่อให้สะท้อนถึง C ++ 11 และที่อยู่ความคิดเห็นของ @ RainerBlome
einpoklum

@RainerBlome: เราเห็นด้วย แมโค RAISE_ERROR_STL นั้นเป็น pre-C ++ 11 ดังนั้นในบริบทนั้นจะมีการพิสูจน์อย่างสมบูรณ์ ความเข้าใจของฉัน (แต่ฉันไม่เคยมีโอกาสจัดการกับคุณลักษณะเฉพาะเหล่านั้น) คือคุณสามารถใช้เทมเพลต variadic (หรือมาโคร?) ใน Modern C ++ เพื่อแก้ปัญหาได้อย่างสวยงามยิ่งขึ้น
paercebal

@einpoklum: "โปรดอัปเดตคำตอบของคุณเพื่อให้สะท้อนถึง C ++ 11 และพูดถึงความคิดเห็นของ RainerBlome" หมายเลข :-) . . ฉันเชื่อว่าอย่างดีที่สุดฉันจะเพิ่มส่วนสำหรับ Modern C ++ ด้วยการใช้งานทางเลือกเพื่อลดหรือกำจัดความจำเป็นในการใช้งานแมโคร แต่จุดยืน: Macros นั้นน่าเกลียดและชั่วร้าย แต่เมื่อคุณต้องทำสิ่งที่คอมไพเลอร์ไม่เข้าใจ คุณทำได้ผ่านมาโคร
paercebal

แม้ว่าจะมี C ++ 11 แต่แมโครของคุณก็ยังสามารถใช้งานได้: #include <sstream> #include <iostream> using namespace std; void trace(char const * file, int line, ostream & o) { cerr<<file<<":"<<line<<": "<< static_cast<ostringstream & >(o).str().c_str()<<endl; } struct Oss { ostringstream s; ostringstream & lval() { return s; } }; #define TRACE(ostreamstuff) trace(__FILE__, __LINE__, Oss().lval()<<ostreamstuff) int main() { TRACE("Hello " << 123); return 0; }วิธีนั้นมาโครจะสั้นลง
Rainer Blome

16

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


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

15

การกลัวตัวประมวลผลล่วงหน้า C นั้นเหมือนกับการกลัวหลอดไส้เพียงเพราะเราได้รับหลอดฟลูออเรสเซนต์ ใช่อดีตสามารถ {ไฟฟ้า | โปรแกรมเมอร์ไม่มีเวลา} ใช่คุณสามารถเผาไหม้พวกเขา (ตามตัวอักษร) แต่พวกเขาสามารถทำงานให้สำเร็จถ้าคุณจัดการกับมันอย่างเหมาะสม

เมื่อคุณโปรแกรมระบบฝังตัว C ใช้เป็นตัวเลือกเดียวนอกเหนือจากแอสเซมเบลอร์แบบฟอร์ม หลังจากเขียนโปรแกรมบนเดสก์ท็อปด้วย C ++ แล้วเปลี่ยนเป็นเป้าหมายฝังตัวที่เล็กลงคุณเรียนรู้ที่จะหยุดกังวลเกี่ยวกับ“ inelegancies” ของฟีเจอร์ C จำนวนมาก (รวมมาโคร) และพยายามหาวิธีการใช้ที่ดีที่สุดและปลอดภัย คุณสมบัติ

Alexander Stepanov พูดว่า :

เมื่อเราเขียนโปรแกรมใน C ++ เราไม่ควรละอายกับมรดก C แต่ควรใช้ให้เต็มที่ ปัญหาเฉพาะกับ C ++ และแม้แต่ปัญหาเฉพาะกับ C เกิดขึ้นเมื่อพวกเขาเองไม่สอดคล้องกับตรรกะของตนเอง


ฉันคิดว่านี่เป็นทัศนคติที่ผิด เพียงเพราะคุณสามารถเรียนรู้ที่จะ "จัดการอย่างถูกต้อง" ไม่ได้หมายความว่ามันคุ้มค่ากับเวลาและความพยายามของทุกคน
Neil G

9

เราใช้__FILE__และ__LINE__มาโครเพื่อวัตถุประสงค์ในการวิเคราะห์ข้อผิดพลาดในการส่งข้อมูลจับและบันทึกพร้อมด้วยเครื่องสแกนไฟล์บันทึกอัตโนมัติในโครงสร้างพื้นฐาน QA ของเรา

ตัวอย่างเช่นแมโครการขว้างปาOUR_OWN_THROWอาจใช้กับประเภทข้อยกเว้นและพารามิเตอร์ตัวสร้างสำหรับข้อยกเว้นนั้นรวมถึงคำอธิบายที่เป็นข้อความ แบบนี้:

OUR_OWN_THROW(InvalidOperationException, (L"Uninitialized foo!"));

แน่นอนว่ามาโครนี้จะโยนInvalidOperationExceptionข้อยกเว้นพร้อมกับคำอธิบายเป็นพารามิเตอร์ตัวสร้าง แต่มันก็จะเขียนข้อความไปยังไฟล์บันทึกที่ประกอบด้วยชื่อไฟล์และหมายเลขบรรทัดที่มีการโยนเกิดขึ้นและคำอธิบายที่เป็นข้อความ ข้อยกเว้นที่ส่งออกไปจะได้รับ ID ซึ่งจะได้รับการบันทึกด้วย หากมีการตรวจพบข้อยกเว้นในที่อื่นรหัสจะถูกทำเครื่องหมายเช่นนั้นและไฟล์บันทึกจะระบุว่ามีการจัดการข้อยกเว้นเฉพาะและไม่น่าจะเป็นสาเหตุของความผิดพลาดที่อาจเข้าสู่ระบบในภายหลัง สามารถรับข้อยกเว้นที่ไม่สามารถจัดการได้โดยโครงสร้างพื้นฐาน QA อัตโนมัติของเรา


9

การทำซ้ำรหัส

ลองดูเพื่อเพิ่มไลบรารี preprocessorมันเป็นประเภทของการเขียนโปรแกรม meta-meta ในหัวข้อ -> แรงบันดาลใจคุณสามารถหาตัวอย่างที่ดี


ฉันเกือบทั้งหมดถ้าไม่ทั้งหมดกรณี - การซ้ำรหัสสามารถหลีกเลี่ยงได้ด้วยการเรียกใช้ฟังก์ชัน
einpoklum

@einpoklum: ฉันไม่เห็นด้วย ไปดูที่ลิงค์
Ruggero Turra

9

บางสิ่งที่ทันสมัยและมีประโยชน์มากยังสามารถสร้างขึ้นได้โดยใช้ preprocessor (มาโคร) ซึ่งคุณจะไม่สามารถทำได้โดยใช้ c ++ "โครงสร้างภาษา" รวมถึงแม่แบบ

ตัวอย่าง:

ทำบางสิ่งบางอย่างทั้งตัวระบุ C และสตริง

วิธีง่ายๆในการใช้ตัวแปรชนิด enum เป็นสตริงใน C

เพิ่ม Metaprogramming การประมวลผลล่วงหน้า


ลิงค์ที่สามเสีย fyi
Robin Hartland

ใช้เวลาดูstdio.hและsal.hแฟ้มในvc12สำหรับการทำความเข้าใจ
Elshan

7

ฉันใช้มาโครเป็นครั้งคราวเพื่อให้ฉันสามารถกำหนดข้อมูลได้ในที่เดียว แต่ใช้ในรูปแบบต่างๆในส่วนต่างๆของรหัส มันชั่วร้ายเพียงเล็กน้อย :)

ตัวอย่างเช่นใน "field_list.h":

/*
 * List of fields, names and values.
 */
FIELD(EXAMPLE1, "first example", 10)
FIELD(EXAMPLE2, "second example", 96)
FIELD(ANOTHER, "more stuff", 32)
...
#undef FIELD

จากนั้นสำหรับ enum สาธารณะก็สามารถกำหนดให้ใช้ชื่อ:

#define FIELD(name, desc, value) FIELD_ ## name,

typedef field_ {

#include "field_list.h"

    FIELD_MAX

} field_en;

และในฟังก์ชั่น init ส่วนตัวเขตข้อมูลทั้งหมดสามารถใช้เพื่อเติมข้อมูลตารางด้วยข้อมูล:

#define FIELD(name, desc, value) \
    table[FIELD_ ## name].desc = desc; \
    table[FIELD_ ## name].value = value;

#include "field_list.h"

1
หมายเหตุ: เทคนิคที่คล้ายกันสามารถใช้งานได้โดยไม่ต้องรวมแยก ดู: stackoverflow.com/questions/147267/... stackoverflow.com/questions/126277/...
Suma

6

การใช้งานทั่วไปอย่างหนึ่งคือการตรวจสอบสภาพแวดล้อมการคอมไพล์สำหรับการพัฒนาข้ามแพลตฟอร์มคุณสามารถเขียนโค้ดหนึ่งชุดสำหรับ linux, พูดและอีกหนึ่งสำหรับ windows เมื่อไม่มีไลบรารีข้ามแพลตฟอร์มสำหรับวัตถุประสงค์ของคุณ

ดังนั้นในตัวอย่างคร่าวๆ Mutex ข้ามแพลตฟอร์มสามารถมีได้

void lock()
{
    #ifdef WIN32
    EnterCriticalSection(...)
    #endif
    #ifdef POSIX
    pthread_mutex_lock(...)
    #endif
}

สำหรับฟังก์ชั่นจะมีประโยชน์เมื่อคุณต้องการละเว้นความปลอดภัยของประเภทอย่างชัดเจน เช่นหลายตัวอย่างด้านบนและด้านล่างสำหรับการทำ ASSERT แน่นอนว่ามีคุณสมบัติ C / C ++ มากมายที่คุณสามารถยิงได้ด้วยตัวเอง แต่ภาษานั้นให้เครื่องมือและให้คุณตัดสินใจว่าจะทำอะไร


ตั้งแต่ผู้ถามถาม: สิ่งนี้สามารถทำได้โดยไม่มีมาโครโดยรวมส่วนหัวที่แตกต่างกันผ่านทางรวมถึงเส้นทางต่อแพลตฟอร์มที่แตกต่างกัน ฉันอยากจะเห็นด้วยว่ามาโครนั้นมักจะสะดวกกว่า
Steve Jessop

ฉันสองที่ หากคุณเริ่มใช้งานมาโครเพื่อจุดประสงค์นั้นโค้ดสามารถอ่านได้ง่ายขึ้นอย่างรวดเร็ว
Nemanja Trifunovic

6

สิ่งที่ต้องการ

void debugAssert(bool val, const char* file, int lineNumber);
#define assert(x) debugAssert(x,__FILE__,__LINE__);

เพื่อให้คุณสามารถยกตัวอย่างเช่น

assert(n == true);

และรับชื่อไฟล์ต้นฉบับและหมายเลขบรรทัดของปัญหาที่พิมพ์ไว้ในบันทึกของคุณหาก n เป็นเท็จ

หากคุณใช้ฟังก์ชั่นการโทรปกติเช่น

void assert(bool val);

แทนที่จะเป็นแมโครสิ่งที่คุณจะได้รับคือหมายเลขบรรทัดของฟังก์ชัน assert ที่พิมพ์ลงในบันทึกซึ่งจะมีประโยชน์น้อยกว่า


ทำไมคุณจะบูรณาการล้อเมื่อการใช้งานของห้องสมุดมาตรฐานแล้วให้ผ่านแมโครซึ่งทิ้งไฟล์ / สาย / ข้อมูลฟังก์ชั่นหรือไม่ (ในการใช้งานทั้งหมดที่ฉันเคยเห็น)<cassert>assert()
underscore_d

4
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

ไม่เหมือนกับโซลูชันเทมเพลต 'ที่ต้องการ' ที่กล่าวถึงในเธรดปัจจุบันคุณสามารถใช้เป็นนิพจน์คงที่:

char src[23];
int dest[ARRAY_SIZE(src)];

2
ซึ่งสามารถทำได้กับแม่แบบในวิธีที่ปลอดภัย (ซึ่งจะไม่รวบรวมถ้าผ่านตัวชี้มากกว่าอาร์เรย์) stackoverflow.com/questions/720077/calculating-size-of-an-array/...
Motti

1
ตอนนี้เรามี constexpr ใน C ++ 11 แล้วเวอร์ชัน safe (ไม่ใช่แมโคร) สามารถใช้ในนิพจน์คงที่ได้ template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }
David Stone

3

คุณสามารถใช้ #defines เพื่อช่วยแก้ไขสถานการณ์การดีบักและทดสอบหน่วย ตัวอย่างเช่นสร้างตัวแปรการบันทึกพิเศษของฟังก์ชั่นหน่วยความจำและสร้าง memlog_preinclude.h พิเศษ:

#define malloc memlog_malloc
#define calloc memlog calloc
#define free memlog_free

รวบรวมรหัสคุณโดยใช้:

gcc -Imemlog_preinclude.h ...

ลิงก์ใน memlog.o ของคุณไปยังรูปภาพสุดท้าย ตอนนี้คุณสามารถควบคุม malloc ฯลฯ อาจจะใช้เพื่อการบันทึกหรือเพื่อจำลองความล้มเหลวในการจัดสรรสำหรับการทดสอบหน่วย


3

เมื่อคุณทำการตัดสินใจในเวลารวบรวมกว่าพฤติกรรมที่เฉพาะเจาะจงของคอมไพเลอร์ / OS / ฮาร์ดแวร์

จะช่วยให้คุณสร้างอินเทอร์เฟซของคุณกับคุณสมบัติเฉพาะของ Comppiler / OS / ฮาร์ดแวร์

#if defined(MY_OS1) && defined(MY_HARDWARE1)
#define   MY_ACTION(a,b,c)      doSothing_OS1HW1(a,b,c);}
#elif define(MY_OS1) && defined(MY_HARDWARE2)
#define   MY_ACTION(a,b,c)      doSomthing_OS1HW2(a,b,c);}
#elif define(MY_SUPER_OS)
          /* On this hardware it is a null operation */
#define   MY_ACTION(a,b,c)
#else
#error  "PLEASE DEFINE MY_ACTION() for this Compiler/OS/HArdware configuration"
#endif

3

ฉันใช้มาโครเพื่อกำหนดข้อยกเว้นได้อย่างง่ายดาย:

DEF_EXCEPTION(RessourceNotFound, "Ressource not found")

โดยที่ DEF_EXCEPTION คือ

#define DEF_EXCEPTION(A, B) class A : public exception\
  {\
  public:\
    virtual const char* what() const throw()\
    {\
      return B;\
    };\
  }\

2

คอมไพเลอร์สามารถปฏิเสธคำขอของคุณเป็นแบบอินไลน์

มาโครจะมีที่อยู่เสมอ

สิ่งที่ฉันพบว่ามีประโยชน์คือ #define DEBUG สำหรับการติดตามการดีบัก - คุณสามารถปล่อยให้มัน 1 ในขณะที่การดีบั๊กปัญหา (หรือแม้แต่ปล่อยให้มันอยู่ในวงจรการพัฒนาทั้งหมด) จากนั้นปิดเมื่อมันถึงเวลาที่จะจัดส่ง


10
หากคอมไพเลอร์ปฏิเสธคำขอของคุณเป็นแบบอินไลน์อาจมีเหตุผลที่ดีมาก คอมไพเลอร์ที่ดีจะดีกว่า inline อย่างถูกต้องมากกว่าที่คุณเป็นและหนึ่งที่ไม่ดีจะให้ปัญหาประสิทธิภาพการทำงานมากกว่านี้
David Thornley

@DavidThornley หรืออาจเป็นคอมไพเลอร์ที่ใช้ประโยชน์ไม่ได้เช่น GCC หรือ CLANG / LLVM คอมไพเลอร์บางตัวเป็นเพียงอึ
Miles Rout

2

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

#define dbgmsg(_FORMAT, ...)  if((debugmsg_flag  & 0x00000001) || (debugmsg_flag & 0x80000000))     { log_dbgmsg(_FORMAT, __VA_ARGS__);  }

เนื่องจาก VA_ARGS ในฟังก์ชันบันทึกนี่เป็นกรณีที่ดีสำหรับแมโครเช่นนี้

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

แมโครที่กำหนดไว้เป็น:

#define SECURITY_CHECK(lRequiredSecRoles) if(!DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, true)) return
#define SECURITY_CHECK_QUIET(lRequiredSecRoles) (DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, false))

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

SECURITY_CHECK(ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR);

LRESULT CAddPerson1::OnWizardNext() 
{
   if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_EMPLOYEE) {
      SECURITY_CHECK(ROLE_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD ) -1;
   } else if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_CONTINGENT) {
      SECURITY_CHECK(ROLE_CONTINGENT_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR) -1;
   }
...

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


2

มาโครตัวอื่น foreach T: type, c: container, i: iterator

#define foreach(T, c, i) for(T::iterator i=(c).begin(); i!=(c).end(); ++i)
#define foreach_const(T, c, i) for(T::const_iterator i=(c).begin(); i!=(c).end(); ++i)

การใช้งาน (แสดงแนวคิดไม่จริง):

void MultiplyEveryElementInList(std::list<int>& ints, int mul)
{
    foreach(std::list<int>, ints, i)
        (*i) *= mul;
}

int GetSumOfList(const std::list<int>& ints)
{
    int ret = 0;
    foreach_const(std::list<int>, ints, i)
        ret += *i;
    return ret;
}

มีการติดตั้งใช้งานที่ดีกว่า: Google "BOOST_FOREACH"

มีบทความดีๆให้อ่าน: ความรักแบบมีเงื่อนไข: FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html


2

บางทีการใช้มาโครของ greates นั้นกำลังอยู่ในระหว่างการพัฒนาโดยไม่ขึ้นกับแพลตฟอร์ม คิดเกี่ยวกับกรณีที่มีประเภทไม่สอดคล้องกัน - ด้วยแมโครคุณสามารถใช้ไฟล์ส่วนหัวที่แตกต่างกัน - เช่น: --WIN_TYPES.H

typedef ...some struct

--POSIX_TYPES.h

typedef ...some another struct

--program.h

#ifdef WIN32
#define TYPES_H "WINTYPES.H"
#else 
#define TYPES_H "POSIX_TYPES.H"
#endif

#include TYPES_H

สามารถอ่านได้มากกว่าการนำไปใช้ในวิธีอื่นในความคิดเห็นของฉัน


2

ดูเหมือนว่า VA_ARGS จะถูกกล่าวถึงทางอ้อมเท่านั้น:

เมื่อเขียนโค้ด C ++ 03 ทั่วไปและคุณต้องการพารามิเตอร์ตัวแปรจำนวน (ทั่วไป) คุณสามารถใช้แมโครแทนแม่แบบ

#define CALL_RETURN_WRAPPER(FnType, FName, ...)          \
  if( FnType theFunction = get_op_from_name(FName) ) {   \
    return theFunction(__VA_ARGS__);                     \
  } else {                                               \
    throw invalid_function_name(FName);                  \
  }                                                      \
/**/

หมายเหตุ:โดยทั่วไปการตรวจสอบชื่อ / การโยนอาจรวมอยู่ในget_op_from_nameฟังก์ชันสมมุติได้เช่นกัน นี่เป็นเพียงตัวอย่าง อาจมีรหัสทั่วไปอื่น ๆ รอบการโทร VA_ARGS

เมื่อเราได้รับเทมเพลตแบบแปรผันด้วย C ++ 11 เราสามารถแก้ปัญหานี้ได้อย่างถูกต้องด้วยเทมเพลต


1

ฉันคิดว่าเคล็ดลับนี้เป็นการใช้ preprocessor อย่างชาญฉลาดที่ไม่สามารถเลียนแบบด้วยฟังก์ชัน:

#define COMMENT COMMENT_SLASH(/)
#define COMMENT_SLASH(s) /##s

#if defined _DEBUG
#define DEBUG_ONLY
#else
#define DEBUG_ONLY COMMENT
#endif

จากนั้นคุณสามารถใช้สิ่งนี้:

cout <<"Hello, World!" <<endl;
DEBUG_ONLY cout <<"This is outputed only in debug mode" <<endl;

คุณยังสามารถกำหนดมาโคร RELEASE_ONLY ได้


2
เคล็ดลับนี้ใช้ไม่ได้ตามมาตรฐาน มันพยายามที่จะสร้างเครื่องหมายความคิดเห็นผ่านตัวประมวลผลล่วงหน้า แต่ความคิดเห็นจะถูกลบออกก่อนที่ตัวประมวลผลล่วงหน้าจะทำงาน คอมไพเลอร์ที่สอดคล้องจะทำให้เกิดข้อผิดพลาดทางไวยากรณ์ที่นี่
David Thornley

2
ขออภัย David แต่คอมไพเลอร์จะต้องมีสำเนาการลบความคิดเห็นที่สอง
Joshua

ง่ายกว่ามากคือการทำให้การตั้งค่าการดีบักเป็นบูล const ส่วนกลางและใช้รหัสดังนี้: if (debug) cout << "... "; - ไม่จำเป็นต้องใช้มาโคร!
สเตฟานโมนอฟ

@ สเตฟาน: อันที่จริงมันเป็นสิ่งที่ฉันทำตอนนี้ คอมไพเลอร์ที่เหมาะสมจะไม่สร้างรหัสใด ๆ หากการแก้ปัญหาเป็นเท็จในกรณีนั้น
Mathieu Pagé

1

คุณสามารถ#defineคงที่ในบรรทัดคำสั่งคอมไพเลอร์โดยใช้-Dหรือ/Dตัวเลือก ซึ่งมักจะมีประโยชน์เมื่อทำการรวบรวมข้ามซอฟต์แวร์เดียวกันสำหรับหลายแพลตฟอร์มเนื่องจากคุณสามารถให้ makefiles ของคุณควบคุมค่าคงที่ที่กำหนดไว้สำหรับแต่ละแพลตฟอร์ม

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