Khi nào các macro C ++ có lợi? [đóng cửa]


177

Bộ tiền xử lý C được cộng đồng C ++ sợ hãi và xa lánh. Các hàm, các hằng và mẫu trong hàng thường là sự thay thế an toàn và vượt trội hơn so với a #define.

Macro sau:

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

không có cách nào vượt trội so với loại an toàn:

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

Nhưng các macro có vị trí của chúng, vui lòng liệt kê các công dụng bạn tìm thấy cho các macro mà bạn không thể thực hiện nếu không có bộ xử lý trước.

Vui lòng đặt từng trường hợp sử dụng vào một câu trả lời riêng biệt để có thể bỏ phiếu và nếu bạn biết cách đạt được một trong những câu trả lời mà không cần người chuẩn bị chỉ ra cách nhận xét của câu trả lời đó.


Có lần tôi đã lấy một ứng dụng C ++ có đầy đủ các macro mất 45 phút để xây dựng, thay thế các macro bằng các hàm nội tuyến và có quá trình xây dựng xuống dưới 15 phút.
endian


Chủ đề là về các bối cảnh trong đó các macro có lợi, không phải các bối cảnh trong đó chúng là tối ưu.
gạch dưới

Câu trả lời:


123

Như wrappers cho các chức năng debug, để tự động chuyển những thứ như __FILE__, __LINE__, vv:

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

14
Trên thực tế, đoạn gốc: << FILE ":" << vẫn ổn, FILE tạo một hằng chuỗi, sẽ được nối với ":" thành một chuỗi bởi bộ xử lý trước.
Frank Szczerba

12
Điều này chỉ yêu cầu tiền xử lý vì __FILE____LINE__ cũng yêu cầu tiền xử lý. Sử dụng chúng trong mã của bạn giống như một vectơ lây nhiễm cho bộ tiền xử lý.
TED

93

Các phương thức phải luôn luôn đầy đủ, có thể biên dịch mã; macro có thể là các đoạn mã. Do đó, bạn có thể định nghĩa một macro foreach:

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

Và sử dụng nó như vậy:

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

Kể từ C ++ 11, điều này được thay thế bởi vòng lặp dựa trên phạm vi .


6
+1 Nếu bạn đang sử dụng một số cú pháp lặp lặp phức tạp một cách lố bịch, viết macro kiểu foreach có thể giúp mã của bạn dễ đọc và duy trì hơn nhiều. Tôi đã làm nó, nó hoạt động.
postfuturist

9
Hầu hết các bình luận hoàn toàn không liên quan đến điểm macro có thể là các đoạn mã thay vì mã hoàn chỉnh. Nhưng cảm ơn bạn đã nitpicking.
jdmichal

12
Đây là C không phải C ++. Nếu bạn đang làm C ++, bạn nên sử dụng iterators và std :: for_each.
chrish

20
Tôi không đồng ý, chrish. Trước lambda, for_eachlà một điều khó chịu, bởi vì mã mà mỗi phần tử được chạy qua không phải là địa phương cho điểm gọi. foreach, (và tôi đặc biệt khuyên dùng BOOST_FOREACHthay vì giải pháp cuộn bằng tay) hãy để bạn giữ mã gần với trang web lặp, làm cho nó dễ đọc hơn. Điều đó nói rằng, một khi lambda ra mắt, for_eachmột lần nữa có thể là con đường để đi.
GManNickG

8
Và điều đáng chú ý là BOOST_FOREACH tự nó là một vĩ mô (nhưng là một thứ rất chu đáo)
Tyler McHenry

59

Tiêu đề bảo vệ tập tin bắt buộc macro.

Có khu vực nào khác cần macro không? Không nhiều (nếu có).

Có bất kỳ tình huống nào khác được hưởng lợi từ macro? ĐÚNG!!!

Một nơi tôi sử dụng macro là với mã rất lặp đi lặp lại. Ví dụ, khi gói mã C ++ được sử dụng với các giao diện khác (.NET, COM, Python, v.v ...), tôi cần phải bắt các loại ngoại lệ khác nhau. Đây là cách tôi làm điều đó:

#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__); \
}

Tôi phải đặt các sản phẩm khai thác này trong mọi chức năng bao bọc. Thay vì gõ các khối bắt đầy đủ mỗi lần, tôi chỉ gõ:

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

Điều này cũng làm cho bảo trì dễ dàng hơn. Nếu tôi phải thêm một loại ngoại lệ mới, chỉ có một nơi tôi cần thêm nó.

Cũng có những ví dụ hữu ích khác: nhiều trong số đó bao gồm các macro __FILE____LINE__tiền xử lý.

Dù sao, macro rất hữu ích khi được sử dụng một cách chính xác. Macro không phải là xấu xa - lạm dụng của họ là xấu xa.


7
Hầu hết các trình biên dịch hỗ trợ #pragma oncenhững ngày này, vì vậy tôi nghi ngờ các vệ sĩ thực sự cần thiết
1800 THÔNG TIN

13
Chúng là nếu bạn đang viết cho tất cả các trình biên dịch thay vì chỉ hầu hết ;-)
Steve Jessop

30
Vì vậy, thay vì chức năng tiền xử lý di động, tiêu chuẩn, bạn khuyên bạn nên sử dụng tiện ích mở rộng tiền xử lý để tránh sử dụng bộ tiền xử lý? Có vẻ như vô lý với tôi.
Logan Capaldo

#pragma oncephá vỡ trên nhiều hệ thống xây dựng phổ biến.
Miles Rout

4
Có một giải pháp cho việc không yêu cầu macro : void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }. Và về phía chức năng:void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
MikeMB

51

Hầu hết:

  1. Bao gồm bảo vệ
  2. Biên soạn có điều kiện
  3. Báo cáo (các macro được xác định trước như __LINE____FILE__)
  4. (hiếm khi) Sao chép các mẫu mã lặp đi lặp lại.
  5. Trong mã của đối thủ cạnh tranh của bạn.

Tìm kiếm một số trợ giúp về cách nhận ra số 5. ​​Bạn có thể hướng dẫn tôi hướng tới một giải pháp không?
Tối đa

50

Bên trong trình biên dịch có điều kiện, để khắc phục các vấn đề về sự khác biệt giữa các trình biên dịch:

#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
Trong C ++, điều tương tự có thể đạt được thông qua việc sử dụng các hàm nội tuyến: <code> #ifdef ARE_we_ON_WIN32 <br> inline int close (int i) {return _close (i); } <br> #endif </ code>
paercebal

2
Điều đó loại bỏ # định nghĩa, nhưng không xóa #ifdef và #endif. Dù sao, tôi đồng ý với bạn.
Gorpik

19
KHÔNG BAO GIỜ xác định macro trường hợp thấp hơn. Macro để thay đổi chức năng là cơn ác mộng của tôi (cảm ơn Microsoft). Ví dụ tốt nhất là trong dòng đầu tiên. Nhiều thư viện có closechức năng hoặc phương thức. Sau đó, khi bạn bao gồm tiêu đề của thư viện này và tiêu đề với macro này hơn là bạn gặp vấn đề lớn, bạn không thể sử dụng API thư viện.
Marek R

AndrewStein, bạn có thấy bất kỳ lợi ích nào đối với việc sử dụng macro trong ngữ cảnh này qua đề xuất của @ paercebal không? Nếu không, có vẻ như macro thực sự vô cớ.
einpoklum

1
#ifdef WE_ARE_ON_WIN32plz :)
Các cuộc đua nhẹ nhàng trong quỹ đạo

38

Khi bạn muốn tạo một chuỗi từ một biểu thức, ví dụ tốt nhất cho điều này là assert( #xbiến giá trị của xchuỗi thành một chuỗi).

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

5
Chỉ là một nitlog, nhưng cá nhân tôi sẽ bỏ dấu chấm phẩy.
Michael Myers

10
Tôi đồng ý, trên thực tế tôi sẽ đặt nó trong một {} while (false) (để ngăn chặn việc đánh dấu khác) nhưng tôi muốn giữ cho nó đơn giản.
Motti

33

Các hằng chuỗi đôi khi được định nghĩa tốt hơn là các macro vì bạn có thể làm nhiều hơn với chuỗi ký tự chuỗi hơn là với a const char *.

ví dụ: chuỗi ký tự có thể dễ dàng nối .

#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);

Nếu một const char *đã được sử dụng thì một số loại chuỗi chuỗi sẽ phải được sử dụng để thực hiện nối chuỗi khi chạy:

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
Trong C ++ 11, tôi coi đây là phần quan trọng nhất (ngoài phần bảo vệ). Macro thực sự là thứ tốt nhất mà chúng ta có để xử lý chuỗi thời gian biên dịch. Đó là một tính năng mà tôi hy vọng chúng ta có được trong C ++ 11 ++
David Stone

1
Đây là tình huống dẫn đến tôi mong muốn các macro trong C #.
Rawling

2
Tôi ước tôi có thể +42 cái này. Một khía cạnh rất quan trọng, mặc dù không thường được ghi nhớ của chuỗi ký tự.
Daniel Kamil Kozar

24

Khi bạn muốn thay đổi các chương trình dòng chảy ( return, breakcontinuecode) trong một chức năng cư xử khác với mã số đó là thực sự inlined trong hàm.

#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.

Ném một ngoại lệ đối với tôi như một sự thay thế tốt hơn.
einpoklum

Khi viết các phần mở rộng python C (++), các ngoại lệ được lan truyền bằng cách đặt một chuỗi ngoại lệ, sau đó trả về -1hoặc NULL. Vì vậy, một macro có thể làm giảm đáng kể mã soạn sẵn ở đó.
black_puppydog

20

Rõ ràng bao gồm bảo vệ

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif

17

Bạn không thể thực hiện đoản mạch các đối số gọi hàm bằng cách gọi hàm thông thường. Ví dụ:

#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
Có thể một điểm chung hơn: các hàm đánh giá chính xác các đối số của chúng một lần. Macro có thể đánh giá các đối số nhiều lần hoặc ít lần hơn.
Steve Jessop

@ [Greg Rogers] tất cả các bộ tiền xử lý macro làm là văn bản thay thế. Một khi bạn hiểu điều đó, sẽ không có nhiều bí ẩn về nó.
1800 THÔNG TIN

Bạn có thể có được hành vi tương đương bằng cách templatizing andf thay vì buộc đánh giá thành bool trước khi gọi hàm. Tôi sẽ không nhận ra những gì bạn nói là đúng mà không tự mình thử nó. Hấp dẫn.
Greg Rogers

Làm thế nào chính xác bạn có thể làm điều đó với một mẫu?
1800 THÔNG TIN

6
Ẩn các hoạt động ngắn mạch đằng sau macro kiểu chức năng là một trong những điều tôi thực sự không muốn thấy trong mã sản xuất.
MikeMB

17

Hãy nói rằng chúng ta sẽ bỏ qua những điều rõ ràng như bảo vệ tiêu đề.

Đôi khi, bạn muốn tạo mã cần được sao chép / dán bởi trình biên dịch trước:

#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)

cho phép bạn viết mã này:

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

Và có thể tạo tin nhắn như:

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

Lưu ý rằng việc trộn các mẫu với macro có thể dẫn đến kết quả thậm chí tốt hơn (nghĩa là tự động tạo các giá trị song song với tên biến của chúng)

Những lần khác, bạn cần __FILE__ và / hoặc __LINE__ của một số mã, để tạo thông tin gỡ lỗi, ví dụ. Sau đây là một tác phẩm kinh điển cho Visual C ++:

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

Như với đoạn mã sau:

#pragma message(WRNG "Hello World")

nó tạo ra các thông điệp như:

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

Những lần khác, bạn cần tạo mã bằng cách sử dụng các toán tử ghép nối # và ##, như tạo getters và setters cho một thuộc tính (điều này dành cho các trường hợp khá hạn chế, thông qua).

Những lần khác, bạn sẽ tạo mã hơn là không biên dịch nếu được sử dụng thông qua một hàm, như:

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

Mà có thể được sử dụng như

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

(tuy nhiên, tôi chỉ thấy loại mã này được sử dụng đúng một lần )

Cuối cùng, nhưng không kém phần quan trọng, nổi tiếng 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;
}

(Lưu ý: sao chép mã / dán từ trang chủ boost)

Đó là cách (IMHO) tốt hơn std::for_each.

Vì vậy, macro luôn hữu ích vì chúng nằm ngoài các quy tắc biên dịch thông thường. Nhưng tôi thấy rằng hầu hết thời gian tôi nhìn thấy một, chúng vẫn là mã C không bao giờ được dịch thành C ++ thích hợp.


1
Chỉ sử dụng CPP cho những gì trình biên dịch không thể làm. Ví dụ, RAISE_ERROR_STL chỉ nên sử dụng CPP để xác định tệp, dòng và chữ ký hàm và chuyển chúng cho một hàm (có thể là nội tuyến) mà phần còn lại.
Rainer Blome 4/2/2016

Vui lòng cập nhật câu trả lời của bạn để phản ánh C ++ 11 và địa chỉ bình luận của @ RainerBlome.
einpoklum

@RainerBlome: Chúng tôi đồng ý. Macro RAISE_ERROR_STL là tiền C ++ 11, vì vậy trong bối cảnh đó, nó hoàn toàn hợp lý. Sự hiểu biết của tôi (nhưng tôi chưa bao giờ có cơ hội để xử lý các tính năng cụ thể đó) là bạn có thể sử dụng các mẫu biến đổi (hoặc macro?) Trong Modern C ++ để giải quyết vấn đề một cách thanh lịch hơn.
paercebal

@einpoklum: "Vui lòng cập nhật câu trả lời của bạn để phản ánh C ++ 11 và giải quyết nhận xét của RainerBlome" Số :-). . . Tôi tin rằng, tốt nhất, tôi sẽ thêm một phần cho Modern C ++, với các triển khai thay thế làm giảm hoặc loại bỏ sự cần thiết của macro, nhưng quan điểm: Macros là xấu và xấu, nhưng khi bạn cần làm gì đó thì trình biên dịch không hiểu , bạn làm điều đó thông qua các macro.
paercebal

Ngay cả với C ++ 11, rất nhiều thứ mà macro của bạn có thể để một hàm thực hiện: Theo #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; }cách đó, macro ngắn hơn nhiều.
Rainer Blome

16

Các khung kiểm tra đơn vị cho C ++ như UnitTest ++ xoay quanh các macro tiền xử lý. Một vài dòng mã kiểm tra đơn vị mở rộng thành một hệ thống phân cấp các lớp hoàn toàn không thú vị để nhập thủ công. Không có thứ gì đó như UnitTest ++ và đó là phép thuật tiền xử lý, tôi không biết bạn sẽ viết các bài kiểm tra đơn vị cho C ++ một cách hiệu quả như thế nào.


Unittests là hoàn toàn có thể viết mà không cần một khuôn khổ. Cuối cùng, nó chỉ thực sự phụ thuộc vào loại đầu ra mà bạn muốn. Nếu bạn không quan tâm, một giá trị thoát đơn giản cho thấy thành công hay thất bại sẽ hoàn toàn ổn.
Rõ ràng hơn

15

Sợ tiền xử lý C cũng giống như sợ bóng đèn sợi đốt chỉ vì chúng ta có bóng đèn huỳnh quang. Có, trước đây có thể là {điện | thời gian lập trình} không hiệu quả. Vâng, bạn có thể bị đốt cháy (theo nghĩa đen) bởi họ. Nhưng họ có thể hoàn thành công việc nếu bạn xử lý đúng cách.

Khi bạn lập trình các hệ thống nhúng, C sử dụng làm trình biên dịch biểu mẫu ngoài tùy chọn duy nhất. Sau khi lập trình trên máy tính để bàn với C ++ và sau đó chuyển sang các mục tiêu được nhúng, nhỏ hơn, bạn học cách ngừng lo lắng về việc Inelegancies. Có rất nhiều tính năng C trần (bao gồm các macro) và chỉ cố gắng tìm ra cách sử dụng tốt nhất và an toàn mà bạn có thể nhận được từ những mục tiêu đó đặc trưng.

Alexander Stepanov nói :

Khi chúng ta lập trình trong C ++, chúng ta không nên xấu hổ về di sản C của nó, nhưng hãy tận dụng nó. Các vấn đề duy nhất với C ++ và thậm chí là các vấn đề duy nhất với C, phát sinh khi bản thân chúng không phù hợp với logic riêng của chúng.


Tôi nghĩ rằng đây là thái độ sai. Chỉ vì bạn có thể học cách "xử lý đúng cách" không có nghĩa là nó đáng giá thời gian và công sức của bất kỳ ai.
Neil G

9

Chúng tôi sử dụng __FILE____LINE__macro cho mục đích chẩn đoán trong ném, bắt và ghi nhật ký ngoại lệ giàu thông tin, cùng với máy quét tệp nhật ký tự động trong cơ sở hạ tầng QA của chúng tôi.

Ví dụ, macro ném OUR_OWN_THROWcó thể được sử dụng với các tham số loại và hàm tạo ngoại lệ cho ngoại lệ đó, bao gồm mô tả văn bản. Như thế này:

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

Macro này tất nhiên sẽ ném InvalidOperationExceptionngoại lệ với mô tả là tham số của hàm tạo, nhưng nó cũng sẽ viết một thông điệp tới tệp nhật ký bao gồm tên tệp và số dòng nơi ném xảy ra và mô tả văn bản của nó. Ngoại lệ ném sẽ nhận được một id, cũng được ghi lại. Nếu ngoại lệ được bắt gặp ở một nơi khác trong mã, nó sẽ được đánh dấu như vậy và tệp nhật ký sẽ chỉ ra rằng ngoại lệ cụ thể đó đã được xử lý và do đó không có khả năng là nguyên nhân của bất kỳ sự cố nào có thể được ghi lại sau này. Các ngoại lệ chưa được xử lý có thể dễ dàng được chọn bởi cơ sở hạ tầng QA tự động của chúng tôi.


9

Mã lặp lại.

Có một cái nhìn để tăng cường thư viện tiền xử lý , đó là một loại lập trình meta-meta. Trong chủ đề-> động lực bạn có thể tìm thấy một ví dụ tốt.


Tôi gần như tất cả, nếu không phải tất cả, các trường hợp - sự lặp lại mã có thể tránh được với các lệnh gọi hàm.
einpoklum

@einpoklum: Tôi không đồng ý. Hãy xem liên kết
Ruggero Turra

9

Một số công cụ rất tiên tiến và hữu ích vẫn có thể được xây dựng bằng cách sử dụng bộ tiền xử lý (macro), mà bạn sẽ không bao giờ có thể thực hiện được bằng cách sử dụng "cấu trúc ngôn ngữ" c ++ bao gồm các mẫu.

Ví dụ:

Tạo một cái gì đó cả định danh C và chuỗi

Cách dễ dàng để sử dụng các biến của loại enum như chuỗi trong C

Tăng cường siêu lập trình tiền xử lý


Liên kết thứ ba bị hỏng fyi
Robin Hartland

Hãy xem stdio.hsal.htập tin vc12để hiểu rõ hơn.
El Sơn

7

Thỉnh thoảng tôi sử dụng macro để tôi có thể xác định thông tin ở một nơi, nhưng sử dụng nó theo nhiều cách khác nhau trong các phần khác nhau của mã. Nó chỉ hơi ác :)

Ví dụ: trong "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

Sau đó, đối với một enum công khai, nó có thể được định nghĩa chỉ sử dụng tên:

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

typedef field_ {

#include "field_list.h"

    FIELD_MAX

} field_en;

Và trong một hàm init riêng, tất cả các trường có thể được sử dụng để điền vào bảng với dữ liệu:

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

#include "field_list.h"

1
Lưu ý: kỹ thuật tương tự có thể được thực hiện ngay cả khi không có phần riêng biệt. Xem: stackoverflow.com/questions/147267/... stackoverflow.com/questions/126277/...
Suma

6

Một cách sử dụng phổ biến là phát hiện môi trường biên dịch, để phát triển đa nền tảng, bạn có thể viết một bộ mã cho linux, giả sử và một bộ khác cho các cửa sổ khi không có thư viện đa nền tảng cho mục đích của bạn.

Vì vậy, trong một ví dụ sơ bộ, một mutex đa nền tảng có thể có

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

Đối với các chức năng, chúng rất hữu ích khi bạn muốn bỏ qua loại an toàn. Chẳng hạn như nhiều ví dụ trên và dưới đây để làm ASSERT. Tất nhiên, giống như nhiều tính năng C / C ++ bạn có thể tự bắn vào chân mình, nhưng ngôn ngữ cung cấp cho bạn các công cụ và cho phép bạn quyết định phải làm gì.


Vì người hỏi đã hỏi: điều này có thể được thực hiện mà không cần macro bằng cách bao gồm các tiêu đề khác nhau thông qua các đường dẫn bao gồm khác nhau trên mỗi nền tảng. Tôi có khuynh hướng đồng ý mặc dù các macro thường thuận tiện hơn.
Steve Jessop

Tôi thứ hai. Nếu bạn bắt đầu sử dụng macro cho mục đích đó, mã có thể nhanh chóng trở nên dễ đọc hơn nhiều
Nemanja Trifunovic

6

Cái gì đó như

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

Vì vậy, ví dụ bạn có thể có

assert(n == true);

và lấy tên tệp nguồn và số dòng của vấn đề được in ra nhật ký của bạn nếu n sai.

Nếu bạn sử dụng một cuộc gọi chức năng bình thường như

void assert(bool val);

thay vì macro, tất cả những gì bạn có thể nhận được là số dòng của hàm khẳng định của bạn được in vào nhật ký, sẽ ít hữu ích hơn.


Tại sao bạn sẽ phát minh lại bánh xe khi triển khai bộ thư viện chuẩn đã cung cấp thông qua <cassert>các assert()vĩ mô, trong đó bãi tập tin / line / chức năng thông tin? (trong tất cả các triển khai tôi đã thấy, dù sao)
underscore_d

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

Không giống như giải pháp mẫu 'ưa thích' được thảo luận trong một luồng hiện tại, bạn có thể sử dụng nó như một biểu thức không đổi:

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

2
Điều này có thể được thực hiện với các mẫu theo cách an toàn hơn (sẽ không biên dịch nếu được truyền một con trỏ chứ không phải là một mảng) stackoverflow.com/questions/720077/calculating-size-of-an-array/
trộm

1
Bây giờ chúng ta có constexpr trong C ++ 11, phiên bản an toàn (không phải macro) cũng có thể được sử dụng trong một biểu thức không đổi. template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }
David Stone

3

Bạn có thể sử dụng #defines để giúp gỡ lỗi và kiểm tra kịch bản đơn vị. Ví dụ: tạo các biến thể ghi nhật ký đặc biệt của các hàm bộ nhớ và tạo một memlog_preinclude.h đặc biệt:

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

Biên dịch mã của bạn bằng cách sử dụng:

gcc -Imemlog_preinclude.h ...

Một liên kết trong memlog.o của bạn đến hình ảnh cuối cùng. Bây giờ bạn kiểm soát malloc, v.v., có lẽ cho mục đích ghi nhật ký hoặc để mô phỏng các lỗi phân bổ cho các bài kiểm tra đơn vị.


3

Khi bạn đưa ra quyết định tại thời điểm biên dịch đối với hành vi cụ thể của Trình biên dịch / HĐH / Phần cứng.

Nó cho phép bạn tạo giao diện của mình thành các tính năng cụ thể của Comppiler / OS / Phần cứng.

#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

Tôi sử dụng macro để dễ dàng xác định Ngoại lệ:

DEF_EXCEPTION(RessourceNotFound, "Ressource not found")

trong đó DEF_EXCEPTION là

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

2

Trình biên dịch có thể từ chối yêu cầu của bạn để nội tuyến.

Macro sẽ luôn có vị trí của chúng.

Một cái gì đó tôi thấy hữu ích là #define DEBUG để theo dõi gỡ lỗi - bạn có thể để nó 1 trong khi gỡ lỗi một vấn đề (hoặc thậm chí để lại nó trong toàn bộ chu kỳ phát triển) sau đó tắt nó khi đến lúc xuất xưởng.


10
Nếu trình biên dịch từ chối yêu cầu của bạn để nội tuyến, nó có thể có một lý do rất tốt. Một trình biên dịch tốt sẽ tốt hơn trong việc đặt nội tuyến đúng hơn bạn và một trình biên dịch xấu sẽ cung cấp cho bạn nhiều vấn đề về hiệu năng hơn mức này.
David Thornley

@DavidThornley Hoặc nó có thể không phải là trình biên dịch tối ưu hóa tuyệt vời như GCC hoặc CLANG / LLVM. Một số trình biên dịch chỉ là tào lao.
Miles Rout

2

Trong công việc cuối cùng của tôi, tôi đã làm việc trên một máy quét virus. Để giúp tôi dễ dàng gỡ lỗi hơn, tôi đã đăng nhập rất nhiều ở mọi nơi, nhưng trong một ứng dụng có nhu cầu cao như vậy, chi phí cho một cuộc gọi chức năng là quá đắt. Vì vậy, tôi đã đưa ra Macro nhỏ này, nó vẫn cho phép tôi kích hoạt tính năng ghi nhật ký gỡ lỗi trên phiên bản phát hành tại trang web của khách hàng, mà không phải trả phí cho chức năng gọi sẽ kiểm tra cờ gỡ lỗi và chỉ quay lại mà không đăng nhập bất cứ thứ gì, hoặc nếu được bật , sẽ thực hiện ghi nhật ký ... Macro được xác định như sau:

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

Do VA_ARGS trong các hàm nhật ký, đây là trường hợp tốt cho một macro như thế này.

Trước đó, tôi đã sử dụng macro trong một ứng dụng bảo mật cao cần nói với người dùng rằng họ không có quyền truy cập chính xác và nó sẽ cho họ biết họ cần cờ nào.

(Các) Macro được định nghĩa là:

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

Sau đó, chúng tôi chỉ có thể rắc các kiểm tra lên khắp UI và nó sẽ cho bạn biết vai trò nào được phép thực hiện hành động mà bạn đã cố gắng thực hiện, nếu bạn chưa có vai trò đó. Lý do cho hai trong số họ là trả về một giá trị ở một số nơi và trả về từ một hàm void ở những nơi khác ...

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;
   }
...

Dù sao, đó là cách tôi đã sử dụng chúng, và tôi không chắc làm thế nào điều này có thể được trợ giúp với các mẫu ... Ngoài ra, tôi cố gắng tránh chúng, trừ khi THỰC SỰ cần thiết.


2

Một macro foreach khác. T: loại, c: container, i: lặp

#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)

Cách sử dụng (khái niệm hiển thị, không có thực):

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;
}

Triển khai tốt hơn có sẵn: Google "BOOST_FOREACH"

Bài viết hay có sẵn: Tình yêuđiều kiện: FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html


2

Có thể việc sử dụng macro tuyệt vời là trong sự phát triển độc lập với nền tảng. Hãy suy nghĩ về các trường hợp không nhất quán về loại - với macro, bạn chỉ cần sử dụng các tệp tiêu đề khác nhau - như: --WIN_TYPES.H

typedef ...some struct

--POSIX_TYPES.h

typedef ...some another struct

- chương trình.h

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

#include TYPES_H

Theo tôi, nhiều điều dễ đọc hơn là thực hiện nó theo những cách khác.


2

Có vẻ VA_ARGS chỉ được đề cập gián tiếp cho đến nay:

Khi viết mã C ++ 03 chung và bạn cần số lượng tham số (chung) thay đổi, bạn có thể sử dụng macro thay vì mẫu.

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

Lưu ý: Nói chung, kiểm tra / ném tên cũng có thể được tích hợp vào get_op_from_namechức năng giả thuyết . Đây chỉ là một ví dụ. Có thể có mã chung khác xung quanh lệnh gọi VA_ARGS.

Khi chúng tôi nhận được các mẫu matrixdic với C ++ 11, chúng tôi có thể giải quyết "đúng" này bằng một mẫu.


1

Tôi nghĩ thủ thuật này là một cách sử dụng thông minh của bộ tiền xử lý không thể được mô phỏng bằng một hàm:

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

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

Sau đó, bạn có thể sử dụng nó như thế này:

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

Bạn cũng có thể xác định một macro RELEASE_ONLY.


2
Thủ thuật này không hoạt động theo tiêu chuẩn. Nó cố gắng tạo một điểm đánh dấu nhận xét thông qua bộ tiền xử lý, nhưng các bình luận sẽ bị xóa trước khi bộ tiền xử lý chạy. Một trình biên dịch tuân thủ sẽ gây ra lỗi cú pháp ở đây.
David Thornley

2
Xin lỗi David nhưng trình biên dịch phải chứa bản sao thứ hai loại bỏ nhận xét.
Joshua

dễ dàng hơn nhiều là làm cho cờ gỡ lỗi trở thành một const bool toàn cầu và sử dụng mã như thế này: if (debug) cout << "..."; - không cần macro!
Stefan Monov

@Stefan: Thật vậy, đó là những gì tôi làm bây giờ. Bất kỳ trình biên dịch phong nha sẽ không tạo ra bất kỳ mã nào nếu gỡ lỗi là sai trong trường hợp đó.
Mathieu Pagé

1

Bạn có thể #definehằng số trên dòng lệnh trình biên dịch bằng cách sử dụng -Dhoặc /Dtùy chọn. Điều này thường hữu ích khi biên dịch chéo cùng một phần mềm cho nhiều nền tảng vì bạn có thể kiểm soát tệp tạo tệp của mình những hằng số được xác định cho mỗi nền tảng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.