Makro sama seperti alat lainnya - palu yang digunakan dalam pembunuhan tidak jahat karena ini adalah palu. Cara orang menggunakannya dengan cara itu adalah kejahatan. Jika Anda ingin palu dengan palu, palu adalah alat yang sempurna.
Ada beberapa aspek pada makro yang membuatnya "buruk" (saya akan mengembangkannya nanti, dan menyarankan alternatif):
- Anda tidak dapat men-debug makro.
- Ekspansi makro dapat menyebabkan efek samping yang aneh.
- Makro tidak memiliki "namespace", jadi jika Anda memiliki makro yang bentrok dengan nama yang digunakan di tempat lain, Anda mendapatkan penggantian makro di tempat yang tidak Anda inginkan, dan ini biasanya mengarah ke pesan kesalahan yang aneh.
- Makro dapat memengaruhi hal-hal yang tidak Anda sadari.
Jadi mari kita kembangkan sedikit di sini:
1) Makro tidak dapat di-debug.
Ketika Anda memiliki makro yang diterjemahkan menjadi angka atau string, kode sumber akan memiliki nama makro, dan banyak debugger, Anda tidak dapat "melihat" untuk apa makro diterjemahkan. Jadi, Anda sebenarnya tidak tahu apa yang sedang terjadi.
Penggantian : Gunakan enum
atauconst T
Untuk makro "seperti fungsi", karena debugger bekerja pada tingkat "per baris sumber di mana Anda berada", makro Anda akan bertindak seperti pernyataan tunggal, tidak peduli apakah itu satu atau seratus pernyataan. Membuat sulit untuk mengetahui apa yang sedang terjadi.
Penggantian : Gunakan fungsi - sebaris jika perlu "cepat" (tetapi berhati-hatilah karena terlalu banyak sebaris bukanlah hal yang baik)
2) Ekspansi makro dapat memiliki efek samping yang aneh.
Yang terkenal adalah #define SQUARE(x) ((x) * (x))
dan kegunaannya x2 = SQUARE(x++)
. Itu mengarah ke x2 = (x++) * (x++);
, yang, bahkan jika itu adalah kode yang valid [1], hampir pasti tidak akan menjadi apa yang diinginkan oleh programmer. Jika itu adalah sebuah fungsi, tidak masalah untuk melakukan x ++, dan x hanya akan bertambah sekali.
Contoh lainnya adalah "if else" di makro, katakanlah kita memiliki ini:
#define safe_divide(res, x, y) if (y != 0) res = x/y;
lalu
if (something) safe_divide(b, a, x);
else printf("Something is not set...");
Ini benar-benar menjadi hal yang salah ....
Penggantian : fungsi nyata.
3) Makro tidak memiliki namespace
Jika kami memiliki makro:
#define begin() x = 0
dan kami memiliki beberapa kode dalam C ++ yang menggunakan begin:
std::vector<int> v;
... stuff is loaded into v ...
for (std::vector<int>::iterator it = myvector.begin() ; it != myvector.end(); ++it)
std::cout << ' ' << *it;
Sekarang, pesan kesalahan apa yang menurut Anda Anda dapatkan, dan di mana Anda mencari kesalahan [dengan asumsi Anda benar-benar lupa - atau bahkan tidak tahu tentang - makro awal yang ada di beberapa file header yang ditulis orang lain? [dan bahkan lebih menyenangkan jika Anda memasukkan makro itu sebelum penyertaan - Anda akan tenggelam dalam kesalahan aneh yang sama sekali tidak masuk akal saat Anda melihat kode itu sendiri.
Penggantian : Tidak ada banyak pengganti sebagai "aturan" - hanya gunakan nama huruf besar untuk makro, dan jangan pernah menggunakan semua nama huruf besar untuk hal lain.
4) Makro memiliki efek yang tidak Anda sadari
Ambil fungsi ini:
#define begin() x = 0
#define end() x = 17
... a few thousand lines of stuff here ...
void dostuff()
{
int x = 7;
begin();
... more code using x ...
printf("x=%d\n", x);
end();
}
Sekarang, tanpa melihat makro, Anda akan berpikir bahwa begin adalah fungsi, yang seharusnya tidak mempengaruhi x.
Hal semacam ini, dan saya telah melihat contoh yang jauh lebih kompleks, BENAR-BENAR dapat mengacaukan hari Anda!
Penggantian : Jangan gunakan makro untuk menyetel x, atau berikan x sebagai argumen.
Ada kalanya menggunakan makro pasti menguntungkan. Salah satu contohnya adalah membungkus fungsi dengan makro untuk meneruskan informasi file / baris:
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x) my_debug_free(x, __FILE__, __LINE__)
Sekarang kita dapat menggunakan my_debug_malloc
malloc biasa dalam kode, tetapi memiliki argumen tambahan, jadi ketika sampai pada akhir dan kita memindai "elemen memori mana yang belum dibebaskan", kita dapat mencetak di mana alokasi dibuat sehingga programmer dapat melacak kebocoran.
[1] Ini adalah perilaku tidak terdefinisi untuk memperbarui satu variabel lebih dari sekali "dalam titik urutan". Titik urutan tidak persis sama dengan pernyataan, tetapi untuk sebagian besar maksud dan tujuan, itulah yang harus kita anggap sebagai. Jadi melakukan x++ * x++
akan memperbarui x
dua kali, yang tidak ditentukan dan mungkin akan mengarah pada nilai yang berbeda pada sistem yang berbeda, dan nilai hasil yang berbeda x
juga.
#pragma
bukan makro.