Kapan makro C ++ bermanfaat? [Tutup]

177

The C preprocessor adalah dibenarkan ditakuti dan dijauhi oleh C ++ masyarakat. Fungsi in-lined, const dan templat biasanya merupakan alternatif yang lebih aman dan unggul untuk a #define.

Makro berikut:

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

sama sekali tidak lebih unggul dari tipe safe:

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

Tetapi makro memang memiliki tempatnya, harap cantumkan penggunaan yang Anda temukan untuk makro yang tidak dapat Anda lakukan tanpa preprocessor.

Silakan masukkan setiap kasus penggunaan dalam jawaban yang terpisah sehingga dapat dipilih dan jika Anda tahu cara mencapai salah satu jawaban tanpa preprosessor tunjukkan bagaimana dalam komentar jawaban itu.

Motti
sumber
Saya pernah mengambil aplikasi C ++ yang penuh dengan makro yang membutuhkan waktu 45 menit untuk membangun, mengganti makro dengan fungsi inline, dan membuat build menjadi kurang dari 15 menit.
endian
Utas adalah tentang konteks di mana makro bermanfaat, bukan konteks di mana mereka suboptimal.
underscore_d

Jawaban:

123

Sebagai pembungkus untuk fungsi debug, untuk secara otomatis lulus hal-hal seperti __FILE__, __LINE__, dll:

#ifdef ( DEBUG )
#define M_DebugLog( msg )  std::cout << __FILE__ << ":" << __LINE__ << ": " << msg
#else
#define M_DebugLog( msg )
#endif
Frank Szczerba
sumber
14
Sebenarnya, cuplikan asli: << FILE ":" << baik-baik saja, FILE menghasilkan konstanta string, yang akan digabungkan dengan ":" menjadi string tunggal oleh pra-prosesor.
Frank Szczerba
12
Ini hanya memerlukan preprocessor karena __FILE__dan __LINE__ juga memerlukan preprocessor. Menggunakannya dalam kode Anda seperti vektor infeksi untuk preprosesor.
TED
93

Metode harus selalu lengkap, kode dapat dikompilasi; makro mungkin berupa fragmen kode. Dengan demikian Anda dapat mendefinisikan makro foreach:

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

Dan gunakan sebagai berikut:

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

Sejak C ++ 11, ini digantikan oleh range-based untuk loop .

jdmichal
sumber
6
+1 Jika Anda menggunakan sintaks iterator yang sangat rumit, menulis makro gaya foreach dapat membuat kode Anda lebih mudah dibaca dan dipelihara. Saya sudah melakukannya, itu berhasil.
postfuturist
9
Sebagian besar komentar benar-benar tidak relevan sampai-sampai makro mungkin merupakan fragmen kode alih-alih kode lengkap. Tapi terima kasih atas kesalahannya.
jdmichal
12
Ini bukan C ++. Jika Anda melakukan C ++, Anda harus menggunakan iterator dan std :: for_each.
chrish
20
Saya tidak setuju, chrish. Sebelum lambda, for_eachadalah hal yang buruk, karena kode setiap elemen dijalankan tidak lokal ke titik panggilan. foreach, (dan saya sangat merekomendasikan BOOST_FOREACHdaripada solusi linting tangan) mari Anda menjaga kode dekat dengan situs iterasi, membuatnya lebih mudah dibaca. Konon, begitu lambda diluncurkan, for_eachmungkin sekali lagi jalan yang harus ditempuh.
GManNickG
8
Dan perlu dicatat bahwa BOOST_FOREACH itu sendiri adalah makro (tapi yang dipikirkan dengan sangat baik)
Tyler McHenry
59

Pelindung file header mengharuskan makro.

Apakah ada area lain yang membutuhkan makro? Tidak banyak (jika ada).

Apakah ada situasi lain yang mendapat manfaat dari makro? IYA!!!

Satu tempat saya menggunakan macro adalah dengan kode yang sangat berulang. Misalnya, ketika membungkus kode C ++ untuk digunakan dengan antarmuka lain (.NET, COM, Python, dll ...), saya perlu menangkap berbagai jenis pengecualian. Begini cara saya melakukannya:

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

Saya harus meletakkan tangkapan ini di setiap fungsi pembungkus. Daripada mengetik blok tangkapan penuh setiap kali, saya cukup mengetik:

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

Ini juga membuat perawatan lebih mudah. Jika saya harus menambahkan tipe pengecualian baru, hanya ada satu tempat yang perlu saya tambahkan.

Ada juga contoh berguna lainnya: banyak di antaranya termasuk makro __FILE__dan __LINE__preprosesor.

Bagaimanapun, makro sangat berguna jika digunakan dengan benar. Makro tidak jahat - penyalahgunaannya adalah jahat.

Kevin
sumber
7
Kebanyakan penyusun mendukung #pragma oncehari ini, jadi saya ragu penjaga benar-benar diperlukan
1800 INFORMASI
13
Mereka adalah jika Anda menulis untuk semua kompiler, bukan hanya sebagian besar ;-)
Steve Jessop
30
Jadi, alih-alih fungsionalitas preprocessor standar yang portabel, Anda merekomendasikan untuk menggunakan ekstensi preprocessor untuk menghindari penggunaan preprocessor? Sepertinya konyol bagi saya.
Logan Capaldo
#pragma onceistirahat pada banyak sistem build umum.
Rute Mil
4
Ada solusi untuk itu yang tidak memerlukan macro: void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }. Dan di sisi fungsi:void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
MikeMB
51

Kebanyakan:

  1. Termasuk penjaga
  2. Kompilasi bersyarat
  3. Pelaporan (makro yang disukai seperti __LINE__dan __FILE__)
  4. (Jarang) Menggandakan pola kode berulang.
  5. Dalam kode pesaing Anda.
David Thornley
sumber
Mencari bantuan tentang cara mewujudkan angka 5. Bisakah Anda membimbing saya menuju solusi?
Maks
50

Di dalam kompilasi bersyarat, untuk mengatasi masalah perbedaan antara kompiler:

#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
Andrew Stein
sumber
12
Dalam C ++, hal yang sama dapat diperoleh melalui penggunaan fungsi inline: <code> #ifdef ARE_WE_ON_WIN32 <br> inline int close (int i) {return _close (i); } <br> #endif </code>
paercebal
2
Itu menghapus # define, tetapi bukan #ifdef dan #endif. Bagaimanapun, saya setuju dengan Anda.
Gorpik
19
TIDAK PERNAH mendefinisikan makro huruf kecil. Makro untuk mengubah fungsi adalah mimpi buruk saya (terima kasih Microsoft). Contoh terbaik ada di baris pertama. Banyak perpustakaan memiliki closefungsi atau metode. Kemudian ketika Anda menyertakan header perpustakaan ini dan header dengan makro ini daripada Anda memiliki masalah besar, Anda tidak dapat menggunakan API perpustakaan.
Marek R
AndrewStein, apakah Anda melihat adanya manfaat untuk penggunaan makro dalam konteks ini atas saran @ paercebal? Jika tidak, sepertinya makro sebenarnya serampangan.
einpoklum
1
#ifdef WE_ARE_ON_WIN32plz :)
Lightness Races di Orbit
38

Saat Anda ingin membuat string dari ekspresi, contoh terbaik untuk ini adalah assert( #xmengubah nilai xmenjadi string).

#define ASSERT_THROW(condition) \
if (!(condition)) \
     throw std::exception(#condition " is false");
Motti
sumber
5
Hanya nitpick, tapi saya pribadi akan meninggalkan titik koma.
Michael Myers
10
Saya setuju, sebenarnya saya akan membuatnya do {} while (false) (untuk mencegah highjacking) tetapi saya ingin membuatnya tetap sederhana.
Motti
33

Konstanta string terkadang lebih baik didefinisikan sebagai makro karena Anda dapat melakukan lebih banyak dengan string literal daripada dengan a const char *.

mis. String literal dapat dengan mudah digabungkan .

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

Jika a const char *digunakan maka semacam kelas string harus digunakan untuk melakukan penggabungan saat runtime:

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);
Motti
sumber
2
Di C ++ 11, saya menganggap ini sebagai bagian terpenting (selain menyertakan penjaga). Makro benar-benar hal terbaik yang kami miliki untuk pemrosesan string waktu kompilasi. Itu adalah fitur yang saya harap kita dapatkan di C ++ 11 ++
David Stone
1
Ini adalah situasi yang membuat saya berharap untuk macro di C #.
Rawling
2
Saya berharap saya bisa +42 ini. Aspek string string yang sangat penting, meskipun tidak sering diingat.
Daniel Kamil Kozar
24

Ketika Anda ingin mengubah aliran program ( return, breakdan continue) kode dalam suatu fungsi berperilaku berbeda dari kode yang sebenarnya diuraikan dalam fungsi.

#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.
Motti
sumber
Bagi saya, melempar pengecualian sepertinya merupakan alternatif yang lebih baik.
einpoklum
Saat menulis ekstensi python C (++), pengecualian diperbanyak dengan menyetel string pengecualian, lalu mengembalikan -1atau NULL. Jadi makro dapat sangat mengurangi kode boilerplate di sana.
black_puppydog
20

Yang jelas termasuk penjaga

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif
Kena
sumber
17

Anda tidak dapat melakukan hubungan pendek argumen panggilan fungsi menggunakan panggilan fungsi biasa. Sebagai contoh:

#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
1800 INFORMASI
sumber
3
Mungkin poin yang lebih umum: fungsi mengevaluasi argumen mereka tepat sekali. Makro dapat mengevaluasi argumen lebih banyak atau lebih sedikit.
Steve Jessop
@ [Greg Rogers] yang dilakukan preprocessor makro hanyalah teks pengganti. Setelah Anda memahami itu, seharusnya tidak ada lagi misteri tentang hal itu.
1800 INFORMASI
Anda bisa mendapatkan perilaku yang setara dengan templatizing andf alih-alih memaksa evaluasi untuk bool sebelum memanggil fungsi. Saya tidak akan menyadari apa yang Anda katakan itu benar tanpa mencobanya sendiri. Menarik.
Greg Rogers
Bagaimana tepatnya Anda bisa melakukannya dengan templat?
1800 INFORMASI
6
Menyembunyikan operasi hubungan arus pendek di balik makro gaya fungsi adalah salah satu hal yang saya benar-benar tidak ingin lihat dalam kode produksi.
MikeMB
17

Katakanlah kita akan mengabaikan hal-hal yang jelas seperti pelindung tajuk.

Terkadang, Anda ingin membuat kode yang perlu disalin / ditempelkan oleh 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)

yang memungkinkan Anda untuk kode ini:

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

Dan dapat menghasilkan pesan seperti:

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

Perhatikan bahwa mencampur template dengan makro dapat menghasilkan hasil yang lebih baik (yaitu secara otomatis menghasilkan nilai-nilai berdampingan dengan nama variabel mereka)

Di lain waktu, Anda memerlukan __FILE__ dan / atau __LINE__ dari beberapa kode, untuk menghasilkan info debug, misalnya. Berikut ini adalah klasik untuk Visual C ++:

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

Seperti dengan kode berikut:

#pragma message(WRNG "Hello World")

itu menghasilkan pesan seperti:

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

Di lain waktu, Anda perlu membuat kode menggunakan operator gabungan # dan ##, seperti menghasilkan getter dan setter untuk properti (ini untuk kasus yang cukup terbatas, sampai).

Di lain waktu, Anda akan menghasilkan kode yang tidak dapat dikompilasi jika digunakan melalui fungsi, seperti:

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

Yang bisa digunakan sebagai

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

(masih, saya hanya melihat kode semacam ini benar digunakan sekali )

Terakhir, namun tidak kalah pentingnya, yang terkenal 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;
}

(Catatan: kode salin / tempel dari beranda boost)

Yang (IMHO) jauh lebih baik daripada std::for_each.

Jadi, makro selalu berguna karena mereka berada di luar aturan kompiler normal. Tapi saya menemukan bahwa sebagian besar waktu saya melihat satu, mereka secara efektif tetap kode C tidak pernah diterjemahkan ke dalam C ++ yang tepat.

paercebal
sumber
1
Gunakan CPP hanya untuk apa yang tidak bisa dilakukan oleh kompiler. Misalnya, RAISE_ERROR_STL harus menggunakan CPP hanya untuk menentukan file, baris, dan tanda tangan fungsi, dan meneruskannya ke fungsi (mungkin inline) yang melakukan sisanya.
Rainer Blome
Harap perbarui jawaban Anda untuk mencerminkan C ++ 11 dan alamat komentar @ RainerBlome.
einpoklum
@RainerBlome: Kami setuju. Makro RAISE_ERROR_STL adalah pra-C ++ 11, jadi dalam konteks itu, sepenuhnya dibenarkan. Pemahaman saya (tapi saya belum pernah memiliki kesempatan untuk berurusan dengan fitur-fitur spesifik) adalah bahwa Anda dapat menggunakan templat variadic (atau makro?) Di Modern C ++ untuk menyelesaikan masalah dengan lebih elegan.
paercebal
@einpoklum: "Harap perbarui jawaban Anda untuk mencerminkan C ++ 11 dan alamat komentar RainerBlome" No. :-). . . Saya percaya, paling-paling, saya akan menambahkan bagian untuk Modern C ++, dengan implementasi alternatif mengurangi atau menghilangkan kebutuhan makro, tetapi intinya adalah: Makro jelek dan jahat, tetapi ketika Anda perlu melakukan sesuatu, kompiler tidak mengerti , Anda melakukannya melalui makro.
paercebal
Bahkan dengan C ++ 11, banyak hal yang dapat dilakukan oleh makro Anda agar fungsi dapat dilakukan: #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; }Dengan begitu, makro jauh lebih pendek.
Rainer Blome
16

Kerangka uji unit untuk C ++ seperti UnitTest ++ cukup banyak berputar di sekitar macro preprocessor. Beberapa baris kode tes unit berkembang menjadi hierarki kelas yang tidak menyenangkan untuk mengetik secara manual. Tanpa sesuatu seperti UnitTest ++ dan itu sihir preprosesor, saya tidak tahu bagaimana Anda akan secara efisien menulis tes unit untuk C ++.

Joe
sumber
Unittests sangat mungkin untuk menulis tanpa kerangka kerja. Pada akhirnya, itu hanya benar-benar tergantung pada jenis output yang Anda inginkan. Jika Anda tidak peduli, nilai keluar sederhana yang menunjukkan keberhasilan atau kegagalan harus baik-baik saja.
Clearer
15

Takut preprocessor C seperti takut pada lampu pijar hanya karena kita mendapatkan lampu neon. Ya, yang pertama bisa {listrik | waktu programmer} tidak efisien. Ya, Anda bisa terbakar (secara harfiah) oleh mereka. Tetapi mereka bisa menyelesaikan pekerjaan jika Anda menanganinya dengan benar.

Saat Anda memprogram sistem tertanam, C gunakan untuk menjadi satu-satunya opsi selain form assembler. Setelah memprogram di desktop dengan C ++ dan kemudian beralih ke target yang lebih kecil dan disematkan, Anda belajar untuk berhenti mengkhawatirkan "ketidakberesan" dari begitu banyak fitur C telanjang (termasuk makro) dan hanya mencoba mencari tahu penggunaan terbaik dan aman yang bisa Anda dapatkan dari mereka fitur.

Alexander Stepanov mengatakan :

Ketika kita memprogram dalam C ++ kita seharusnya tidak malu dengan warisan C-nya, tetapi manfaatkan sepenuhnya. Satu-satunya masalah dengan C ++, dan bahkan satu-satunya masalah dengan C, muncul ketika mereka sendiri tidak konsisten dengan logika mereka sendiri.

VictorH
sumber
Saya pikir ini adalah sikap yang salah. Hanya karena Anda dapat belajar untuk "menanganinya dengan benar" tidak berarti itu sepadan dengan waktu dan upaya siapa pun.
Neil G
9

Kami menggunakan __FILE__dan __LINE__makro untuk tujuan diagnostik dalam pengecualian kaya informasi melempar, menangkap dan mencatat, bersama dengan pemindai file log otomatis dalam infrastruktur QA kami.

Misalnya, makro melempar OUR_OWN_THROWdapat digunakan dengan tipe pengecualian dan parameter konstruktor untuk pengecualian itu, termasuk deskripsi tekstual. Seperti ini:

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

Makro ini tentu saja akan melempar InvalidOperationExceptionpengecualian dengan deskripsi sebagai parameter konstruktor, tetapi juga akan menulis pesan ke file log yang terdiri dari nama file dan nomor baris tempat lemparan terjadi dan deskripsi tekstualnya. Pengecualian yang dilemparkan akan mendapatkan id, yang juga akan dicatat. Jika pengecualian pernah tertangkap di tempat lain dalam kode, itu akan ditandai seperti itu dan file log kemudian akan menunjukkan bahwa pengecualian spesifik telah ditangani dan karena itu tidak mungkin menjadi penyebab kecelakaan yang mungkin akan dicatat nanti. Pengecualian yang tidak ditangani dapat dengan mudah diambil oleh infrastruktur QA otomatis kami.

Johann Gerell
sumber
9

Pengulangan kode.

Coba lihat untuk meningkatkan perpustakaan preprocessor , ini semacam meta-meta-programming. Dalam topik-> motivasi Anda dapat menemukan contoh yang baik.

Ruggero Turra
sumber
Saya hampir semua, jika tidak semua, kasus - pengulangan kode dapat dihindari dengan panggilan fungsi.
einpoklum
@einpoklum: Saya tidak setuju. Lihat tautannya
Ruggero Turra
9

Beberapa hal yang sangat canggih dan berguna masih dapat dibangun menggunakan preprocessor (macro), yang Anda tidak akan pernah bisa menggunakan c ++ "konstruksi bahasa" termasuk templat.

Contoh:

Membuat sesuatu sebagai pengidentifikasi C dan sebuah string

Cara mudah untuk menggunakan variabel tipe enum sebagai string dalam C

Tingkatkan Preprocessor Metaprogramming

Suma
sumber
Tautan ketiga rusak
Robin Hartland
Lihatlah stdio.hdan sal.hmasukkan file vc12untuk lebih memahami.
Elshan
7

Saya kadang-kadang menggunakan makro sehingga saya dapat mendefinisikan informasi di satu tempat, tetapi menggunakannya dengan cara yang berbeda di berbagai bagian kode. Itu hanya sedikit jahat :)

Misalnya, di "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

Kemudian untuk enum publik dapat didefinisikan hanya menggunakan nama:

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

typedef field_ {

#include "field_list.h"

    FIELD_MAX

} field_en;

Dan dalam fungsi init pribadi, semua bidang dapat digunakan untuk mengisi tabel dengan data:

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

#include "field_list.h"
Andrew Johnson
sumber
1
Catatan: teknik serupa dapat diterapkan bahkan tanpa menyertakan terpisah. Lihat: stackoverflow.com/questions/147267/… stackoverflow.com/questions/126277/…
Suma
6

Salah satu penggunaan yang umum adalah untuk mendeteksi lingkungan kompilasi, untuk pengembangan lintas platform Anda dapat menulis satu set kode untuk linux, misalnya, dan lainnya untuk windows ketika tidak ada perpustakaan lintas platform yang sudah ada untuk keperluan Anda.

Jadi, dalam contoh kasar, sebuah cross-platform mutex dapat dimiliki

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

Untuk fungsi, mereka berguna ketika Anda ingin secara eksplisit mengabaikan keamanan jenis. Seperti banyak contoh di atas dan di bawah ini untuk melakukan ASSERT. Tentu saja, seperti banyak fitur C / C ++ Anda dapat menembak diri sendiri, tetapi bahasa memberi Anda alat dan memungkinkan Anda memutuskan apa yang harus dilakukan.

Doug T.
sumber
Karena penanya bertanya: ini dapat dilakukan tanpa makro dengan memasukkan header yang berbeda melalui jalur include yang berbeda per platform. Saya cenderung setuju bahwa macro seringkali lebih nyaman.
Steve Jessop
Saya yang kedua itu. Jika Anda mulai menggunakan makro untuk tujuan itu, kode dapat dengan cepat menjadi jauh lebih mudah dibaca
Nemanja Trifunovic
6

Sesuatu seperti

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

Sehingga Anda bisa saja misalnya punya

assert(n == true);

dan dapatkan nama file sumber dan nomor baris masalah yang dicetak ke log Anda jika n salah.

Jika Anda menggunakan panggilan fungsi normal seperti

void assert(bool val);

alih-alih makro, yang bisa Anda dapatkan adalah nomor baris fungsi tegas Anda dicetak ke log, yang akan kurang bermanfaat.

Keshi
sumber
Mengapa Anda menciptakan roda ketika implementasi dari Standard Library sudah berikan melalui <cassert>para assert()makro, yang kesedihan file / line / fungsi Info? (dalam semua implementasi yang saya lihat, sih)
underscore_d
4
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

Tidak seperti solusi template 'lebih disukai' yang dibahas dalam utas saat ini, Anda dapat menggunakannya sebagai ekspresi konstan:

char src[23];
int dest[ARRAY_SIZE(src)];
fizzer
sumber
2
Ini dapat dilakukan dengan templat dengan cara yang lebih aman (yang tidak akan dikompilasi jika melewati pointer daripada array) stackoverflow.com/questions/720077/calculating-size-of-an-ar-array/…
Motti
1
Sekarang kita memiliki constexpr di C ++ 11, versi aman (non-makro) juga dapat digunakan dalam ekspresi konstan. template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }
David Stone
3

Anda dapat menggunakan #defines untuk membantu debugging dan skenario pengujian unit. Misalnya, buat varian pencatatan khusus dari fungsi memori dan buat memlog_preinclude.h khusus:

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

Kompilasi kode Anda menggunakan:

gcc -Imemlog_preinclude.h ...

Tautan di memlog.o Anda ke gambar akhir. Anda sekarang mengontrol malloc, dll, mungkin untuk tujuan logging, atau untuk mensimulasikan kegagalan alokasi untuk tes unit.

Andrew Johnson
sumber
3

Ketika Anda membuat keputusan pada waktu kompilasi atas perilaku spesifik Compiler / OS / Hardware.

Hal ini memungkinkan Anda untuk membuat antarmuka Anda ke fitur spesifik Comppiler / OS / Hardware.

#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
Loki Astari
sumber
3

Saya menggunakan makro untuk dengan mudah mendefinisikan Pengecualian:

DEF_EXCEPTION(RessourceNotFound, "Ressource not found")

di mana DEF_EXCEPTION berada

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

Compiler dapat menolak permintaan Anda untuk inline.

Makro akan selalu memiliki tempat mereka.

Sesuatu yang saya temukan bermanfaat adalah #define DEBUG untuk penelusuran debug - Anda dapat membiarkannya 1 saat men-debug masalah (atau bahkan membiarkannya selama seluruh siklus pengembangan) kemudian matikan saat saatnya untuk mengirim.

berat
sumber
10
Jika kompiler menolak permintaan Anda untuk inline, itu mungkin memiliki alasan yang sangat bagus. Kompiler yang baik akan lebih baik dalam inlining daripada Anda, dan yang buruk akan memberi Anda lebih banyak masalah kinerja daripada ini.
David Thornley
@ Davidvidhorn Atau mungkin bukan kompiler optimisasi hebat seperti GCC atau CLANG / LLVM. Beberapa kompiler hanya omong kosong.
Miles Rout
2

Dalam pekerjaan terakhir saya, saya sedang mengerjakan pemindai virus. Untuk mempermudah saya melakukan debug, saya memiliki banyak logging yang macet di semua tempat, tetapi dalam aplikasi permintaan tinggi seperti itu, biaya pemanggilan fungsi terlalu mahal. Jadi, saya datang dengan Makro kecil ini, yang masih memungkinkan saya mengaktifkan debug logging pada versi rilis di situs pelanggan, tanpa biaya panggilan fungsi akan memeriksa flag debug dan hanya kembali tanpa mencatat apa pun, atau jika diaktifkan , akan melakukan pencatatan ... Makro didefinisikan sebagai berikut:

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

Karena VA_ARGS dalam fungsi log, ini adalah kasus yang bagus untuk makro seperti ini.

Sebelum itu, saya menggunakan makro dalam aplikasi keamanan tinggi yang perlu memberi tahu pengguna bahwa mereka tidak memiliki akses yang benar, dan itu akan memberi tahu mereka bendera apa yang mereka butuhkan.

Makro didefinisikan sebagai:

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

Lalu, kami bisa memercikkan cek di seluruh UI, dan itu akan memberi tahu Anda peran mana yang diizinkan untuk melakukan tindakan yang Anda coba lakukan, jika Anda belum memiliki peran itu. Alasan dua dari mereka adalah untuk mengembalikan nilai di beberapa tempat, dan kembali dari fungsi batal di tempat lain ...

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

Ngomong-ngomong, begitulah cara saya menggunakannya, dan saya tidak yakin bagaimana ini bisa membantu dengan template ... Selain itu, saya mencoba untuk menghindarinya, kecuali BENAR-BENAR diperlukan.

LarryF
sumber
2

Satu lagi makro pendahuluan. T: jenis, c: wadah, 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)

Penggunaan (menunjukkan konsep, tidak nyata):

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

Implementasi yang lebih baik tersedia: Google "BOOST_FOREACH"

Artikel bagus tersedia: Cinta Bersyarat: FOREACH Redux (Eric Niebler) http://www.artima.com/cppsource/foreach.html

Tidak dalam daftar
sumber
2

Mungkin penggunaan greate makro dalam pengembangan platform-independen. Pikirkan tentang kasus tipe inkonsistensi - dengan makro, Anda cukup menggunakan file header yang berbeda - seperti: --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

Jauh lebih mudah dibaca daripada mengimplementasikannya dengan cara lain, menurut saya.

rkellerm
sumber
2

Tampaknya VA_ARGS hanya disebutkan secara tidak langsung sejauh ini:

Saat menulis kode C ++ 03 generik, dan Anda memerlukan sejumlah variabel (generik) parameter, Anda bisa menggunakan makro alih-alih templat.

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

Catatan: Secara umum, pemeriksaan nama / lemparan juga dapat dimasukkan ke dalam get_op_from_namefungsi hipotetis . Ini hanya sebuah contoh. Mungkin ada kode generik lain yang mengelilingi panggilan VA_ARGS.

Setelah kita mendapatkan templat variadic dengan C ++ 11, kita bisa menyelesaikan ini dengan benar dengan templat.

Martin Ba
sumber
1

Saya pikir trik ini adalah penggunaan yang cerdas dari preprocessor yang tidak dapat ditiru dengan suatu fungsi:

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

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

Maka Anda dapat menggunakannya seperti ini:

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

Anda juga dapat mendefinisikan makro RELEASE_ONLY.

Mathieu Pagé
sumber
2
Trik ini tidak berfungsi sesuai standar. Ia mencoba untuk membuat penanda komentar melalui preprocessor, tetapi komentar harus dihapus sebelum preprocessor berjalan. Compiler yang menyesuaikan akan menyebabkan kesalahan sintaksis di sini.
David Thornley
2
Maaf David tetapi kompiler harus berisi salinan penghapusan komentar kedua.
Joshua
jauh lebih mudah untuk membuat flag debugging menjadi bool const global, dan gunakan kode seperti ini: if (debug) cout << "..."; - tidak perlu untuk macro!
Stefan Monov
@Stefan: Memang, itulah yang saya lakukan sekarang. Kompilator yang layak tidak akan menghasilkan kode apa pun jika debug salah dalam kasus itu.
Mathieu Pagé
1

Anda bisa #definekonstanta pada baris perintah kompiler menggunakan opsi -Datau /D. Ini sering berguna ketika mengkompilasi silang perangkat lunak yang sama untuk beberapa platform karena Anda dapat membuat makefile Anda mengontrol konstanta apa yang didefinisikan untuk setiap platform.

bk1e
sumber