Apakah #pragma pernah aman termasuk penjaga?

311

Saya pernah membaca bahwa ada beberapa optimasi kompiler saat menggunakan #pragma onceyang dapat menghasilkan kompilasi yang lebih cepat. Saya tahu itu tidak standar, dan karenanya bisa menimbulkan masalah kompatibilitas lintas platform.

Apakah ini sesuatu yang didukung oleh kebanyakan kompiler modern pada platform non-windows (gcc)?

Saya ingin menghindari masalah kompilasi platform, tetapi juga ingin menghindari pekerjaan ekstra dari penjaga cadangan:

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

Haruskah saya khawatir? Haruskah saya mengeluarkan energi mental lebih lanjut untuk ini?

Ryan Emerle
sumber
3
Setelah mengajukan pertanyaan serupa , saya menemukan bahwa #pragma oncetampaknya menghindari beberapa masalah tampilan kelas di VS 2008. Saya sedang dalam proses menyingkirkan penjaga termasuk dan mengganti mereka semua dengan #pragma oncealasan ini.
SmacL

Jawaban:

189

Menggunakan #pragma onceharus bekerja pada kompiler modern apa pun, tapi saya tidak melihat alasan untuk tidak menggunakan standar #ifndeftermasuk penjaga. Ini bekerja dengan baik. Satu peringatan adalah bahwa GCC tidak mendukung #pragma oncesebelum versi 3.4 .

Saya juga menemukan bahwa, setidaknya pada GCC, ia mengenali standar #ifndeftermasuk penjaga dan mengoptimalkannya , jadi seharusnya tidak lebih lambat dari itu #pragma once.

Zifre
sumber
12
Seharusnya tidak lebih lambat sama sekali (dengan GCC).
Jason Coco
54
Tidak diterapkan seperti itu. Alih-alih, jika file dimulai dengan #ifndef pertama kali dan berakhir dengan # endif, gcc akan mengingatnya dan selalu melompati yang termasuk di masa depan tanpa repot-repot membuka file.
Jason Coco
10
#pragma onceumumnya lebih cepat karena file tidak sedang diproses. ifndef/define/endiftetap memerlukan preprocessing, karena setelah blok ini Anda dapat memiliki sesuatu yang dapat dikompilasi (secara teoritis)
Andrey
13
Dokumen GCC tentang pengoptimalan makro penjaga: gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
Adrian
38
Untuk menggunakan menyertakan penjaga, ada persyaratan tambahan bahwa Anda harus mendefinisikan simbol baru seperti #ifndef FOO_BAR_H, biasanya untuk file seperti "foo_bar.h". Jika nanti Anda mengganti nama file ini, haruskah Anda menyesuaikan guard sertakan agar sesuai dengan konvensi ini? Juga, jika Anda memiliki dua foo_bar.h yang berbeda di dua tempat yang berbeda di pohon kode Anda, Anda harus memikirkan dua simbol yang berbeda untuk masing-masing. Jawaban singkatnya adalah menggunakan #pragma oncedan jika Anda benar-benar perlu mengkompilasi di lingkungan yang tidak mendukungnya, maka lanjutkan dan tambahkan menyertakan penjaga untuk lingkungan itu.
Brandin
329

#pragma once memang memiliki satu kelemahan (selain non-standar) dan itu adalah jika Anda memiliki file yang sama di lokasi yang berbeda (kami memiliki ini karena sistem build kami menyalin file di sekitar) maka kompiler akan berpikir ini adalah file yang berbeda.

Motti
sumber
36
Tetapi Anda juga dapat memiliki dua file dengan nama yang sama di lokasi yang berbeda tanpa harus repot-repot membuat #Define NAMES yang berbeda, yang iften dalam bentuk HEADERFILENAME_H
Vargas
69
Anda juga dapat memiliki dua atau lebih file dengan #define WHATEVER yang sama yang menyebabkan kesenangan tanpa akhir, yang merupakan alasan saya lebih suka menggunakan pragma sekali.
Chris Huang-Leaver
107
Tidak persuasif ... Ubah sistem build menjadi yang tidak menyalin file tetapi menggunakan symlink, atau sertakan file yang sama hanya dari satu lokasi di setiap unit terjemahan. Kedengarannya seperti infrastruktur Anda yang berantakan yang harus ditata ulang.
Yakov Galka
3
Dan jika Anda memiliki file yang berbeda dengan nama yang sama di direktori yang berbeda, pendekatan #ifdef akan berpikir mereka adalah file yang sama. Jadi ada kelemahan untuk satu, dan ada kelemahan untuk yang lain.
rxantos
3
@ rxantos, jika file berbeda nilai #ifdefmakro juga bisa berbeda.
Motti
63

Saya berharap #pragma once(atau sesuatu seperti itu) telah ada dalam standar. Menyertakan penjaga bukanlah masalah besar (tetapi mereka tampaknya agak sulit untuk dijelaskan kepada orang-orang yang belajar bahasa itu), tetapi sepertinya gangguan kecil yang bisa dihindari.

Bahkan, sejak 99,98% dari waktu, #pragma onceperilaku adalah perilaku yang diinginkan, akan lebih baik jika mencegah beberapa pemasukan header secara otomatis ditangani oleh kompiler, dengan #pragmaatau sesuatu untuk memungkinkan termasuk ganda.

Tetapi kami memiliki apa yang kami miliki (kecuali bahwa Anda mungkin tidak memiliki #pragma once).

Michael Burr
sumber
48
Yang saya inginkan adalah #importarahan standar .
John
10
Arahan impor standar akan datang: isocpp.org/blog/2012/11/... Tetapi belum datang. Saya sangat mendukungnya.
Membantu
7
@Membantu Vaporware. Sudah hampir lima tahun sekarang. Mungkin pada tahun 2023 Anda akan kembali ke komentar ini dan mengatakan "Sudah kubilang".
doug65536
Ini bukan vaporware, tetapi hanya pada tahap Spesifikasi Teknis. Modul diimplementasikan dalam Visual Studio 2015 ( blogs.msdn.microsoft.com/vcblog/2015/12/03/... ) dan di dentang ( clang.llvm.org/docs/Modules.html ). Dan itu impor, bukan impor.
Membantu
Harus membuatnya menjadi C ++ 20.
Ionoclast Brigham
36

Saya tidak tahu tentang manfaat kinerja tetapi pasti berhasil. Saya menggunakannya di semua proyek C ++ saya (diberikan saya menggunakan kompiler MS). Saya merasa ini lebih efektif daripada menggunakan

#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif

Itu melakukan pekerjaan yang sama dan tidak mengisi preprocessor dengan makro tambahan.

GCC mendukung #pragma oncesecara resmi pada versi 3.4 .

JaredPar
sumber
25

GCC mendukung #pragma oncesejak 3.4, lihat http://en.wikipedia.org/wiki/Pragma_once untuk dukungan kompiler lebih lanjut.

Keuntungan besar yang saya lihat tentang penggunaan #pragma oncesebagai lawan untuk menyertakan penjaga adalah untuk menghindari kesalahan salin / tempel.

Mari kita hadapi itu: kebanyakan dari kita hampir tidak memulai file header baru dari awal, melainkan hanya menyalin yang sudah ada dan memodifikasinya sesuai kebutuhan kita. Jauh lebih mudah untuk membuat templat yang berfungsi menggunakan #pragma oncealih-alih menyertakan penjaga. Semakin sedikit saya harus memodifikasi template, semakin sedikit kemungkinan saya mengalami kesalahan. Memiliki penjaga yang sama termasuk dalam file yang berbeda mengarah ke kesalahan kompiler aneh dan butuh beberapa waktu untuk mencari tahu apa yang salah.

TL; DR: #pragma oncelebih mudah digunakan.

uceumern
sumber
11

Saya menggunakannya dan saya senang dengan itu, karena saya harus mengetik jauh lebih sedikit untuk membuat header baru. Ini bekerja dengan baik untuk saya dalam tiga platform: Windows, Mac dan Linux.

Saya tidak memiliki informasi kinerja, tetapi saya percaya bahwa perbedaan antara #pragma dan penjaga menyertakan tidak akan ada artinya dibandingkan dengan lambatnya penguraian tata bahasa C ++. Itulah masalah sebenarnya. Cobalah untuk mengkompilasi jumlah file dan baris yang sama dengan kompiler C # misalnya, untuk melihat perbedaannya.

Pada akhirnya, menggunakan penjaga atau pragma, tidak masalah sama sekali.

Edwin Jarvis
sumber
Saya tidak suka #pragma sekali, tapi saya menghargai Anda menunjukkan manfaat relatif ... Penguraian C ++ jauh lebih mahal daripada yang lainnya, di lingkungan operasi "normal". Tidak ada yang mengkompilasi dari sistem file jarak jauh jika waktu kompilasi adalah masalah.
Tom
1
Re C ++ kelambatan penguraian vs C #. Dalam C # Anda tidak perlu menguraikan (secara harfiah) ribuan LOC file header (iostream, siapa?) Untuk setiap file C ++ kecil. Namun, gunakan header yang dikompilasi untuk membuat masalah ini lebih kecil
Eli Bendersky
11

Menggunakan ' #pragma once' mungkin tidak memiliki efek apa pun (tidak didukung di mana-mana - meskipun semakin banyak didukung), jadi Anda harus tetap menggunakan kode kompilasi bersyarat, dalam hal ini, mengapa repot-repot dengan ' #pragma once'? Kompilator mungkin mengoptimalkannya. Itu tergantung pada platform target Anda. Jika semua target Anda mendukungnya, maka maju dan gunakan itu - tetapi itu harus menjadi keputusan sadar karena semua akan hancur jika Anda hanya menggunakan pragma dan kemudian port ke kompiler yang tidak mendukungnya.

Jonathan Leffler
sumber
1
Saya tidak setuju bahwa Anda harus mendukung penjaga. Jika Anda menggunakan #pragma sekali (atau penjaga) ini karena ini menimbulkan beberapa konflik tanpa mereka. Jadi, jika tidak didukung oleh alat rantai Anda, proyek tidak akan dikompilasi dan Anda persis dalam situasi yang sama daripada ketika Anda ingin mengkompilasi beberapa ansi C pada kompiler K&R lama. Anda hanya perlu mendapatkan chaintool yang lebih baru atau mengubah kode untuk menambahkan beberapa penjaga. Semua akan hancur jika program dikompilasi tetapi gagal bekerja.
Kriss
5

Manfaat kinerja adalah dari tidak harus membuka kembali file setelah #pragma pernah dibaca. Dengan penjaga, kompiler harus membuka file (yang dapat memakan waktu) untuk mendapatkan informasi yang seharusnya tidak termasuk konten lagi.

Itu adalah teori hanya karena beberapa kompiler secara otomatis tidak akan membuka file yang tidak memiliki kode baca, untuk setiap unit kompilasi.

Pokoknya, ini bukan kasus untuk semua kompiler, jadi idealnya #pragma harus dihindari untuk kode lintas-platform yang bukan standar sama sekali / tidak memiliki definisi dan efek standar. Namun, secara praktis, ini benar-benar lebih baik daripada penjaga.

Pada akhirnya, saran terbaik yang bisa Anda dapatkan untuk memastikan memiliki kecepatan terbaik dari kompiler Anda tanpa harus memeriksa perilaku masing-masing kompiler dalam hal ini, adalah menggunakan pragma sekaligus dan penjaga.

#ifndef NR_TEST_H
#define NR_TEST_H
#pragma once

#include "Thing.h"

namespace MyApp
{
 // ...
}

#endif

Dengan begitu Anda mendapatkan yang terbaik dari keduanya (cross-platform dan membantu kecepatan kompilasi).

Karena lebih lama untuk mengetik, saya pribadi menggunakan alat untuk membantu menghasilkan semua itu dengan cara yang sangat sumbu (Visual Assist X).

Klaim
sumber
Apakah Visual Studio tidak mengoptimalkan #sertakan penjaga apa adanya? Kompiler lain (lebih baik?) Melakukannya, dan saya membayangkan itu cukup mudah.
Tom
1
Mengapa Anda meletakkan pragmasetelah ifndef? Apakah ada manfaatnya?
user1095108
1
@ user1095108 Beberapa kompiler akan menggunakan pelindung kepala sebagai pembatas untuk mengetahui apakah file tersebut hanya berisi kode yang harus dipakai sekali saja. Jika beberapa kode berada di luar pelindung tajuk, maka seluruh file mungkin dianggap mungkin lebih dari satu kali. Jika kompilator yang sama tidak mendukung pragma sekali, maka itu akan mengabaikan instruksi itu. Oleh karena itu, menempatkan pragma di dalam pelindung tajuk adalah cara paling umum untuk memastikan bahwa setidaknya tajuk pelindung dapat "dioptimalkan".
Klaim
4

Tidak selalu.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566 memiliki contoh yang bagus tentang dua file yang dimaksudkan untuk disertakan, tetapi secara keliru dianggap identik karena cap waktu dan konten yang identik (bukan nama file yang identik) .

Omer
sumber
10
Itu akan menjadi bug di kompiler. (mencoba mengambil jalan pintas yang seharusnya tidak diambil).
rxantos
4
#pragma oncetidak standar, jadi apapun yang dikompilasi oleh kompiler adalah "benar". Tentu saja, maka kita dapat mulai berbicara tentang apa yang "diharapkan" dan apa yang "berguna".
user7610
2

Menggunakan gcc 3.4 dan 4.1 pada pohon yang sangat besar (kadang-kadang memanfaatkan distcc ), saya belum melihat kecepatan ketika menggunakan #pragma sekali sebagai pengganti, atau dalam kombinasi dengan standar termasuk penjaga.

Saya benar-benar tidak melihat bagaimana nilainya berpotensi membingungkan versi gcc lama, atau bahkan kompiler lain karena tidak ada penghematan nyata. Saya belum mencoba semua de-linter, tetapi saya berani bertaruh itu akan membingungkan banyak dari mereka.

Saya juga berharap itu telah diadopsi sejak awal, tetapi saya dapat melihat argumen "Mengapa kita membutuhkan itu jika ifndef bekerja dengan baik?". Mengingat banyak sudut dan kompleksitas C yang gelap, termasuk penjaga adalah salah satu hal yang paling mudah dan menjelaskan sendiri. Jika Anda bahkan memiliki sedikit pengetahuan tentang cara kerja preprosesor, mereka harus jelas.

Namun, jika Anda mengamati peningkatan yang signifikan, perbarui pertanyaan Anda.

Pos Tim
sumber
2

Hari ini sekolah tua termasuk penjaga secepat pragma sekali. Bahkan jika kompilator tidak memperlakukan mereka secara khusus, itu akan tetap berhenti ketika melihat # jika andHATI APA dan APA yang didefinisikan. Membuka file sangat murah hari ini. Bahkan jika ada perbaikan, itu akan berada dalam urutan milidetik.

Saya hanya tidak menggunakan #pragma sekali, karena tidak memiliki manfaat. Untuk menghindari bentrok dengan penjaga menyertakan lainnya saya menggunakan sesuatu seperti: CI_APP_MODULE_FILE_H -> CI = Inisial Perusahaan; APP = Nama aplikasi; sisanya jelas.

CMircea
sumber
19
Bukankah manfaatnya mengetik jauh lebih sedikit?
Andrey
1
Namun perlu dicatat bahwa beberapa milidetik seratus ribu kali adalah beberapa menit. Proyek besar terdiri dari sepuluh ribu file termasuk puluhan header masing-masing. Mengingat banyak CPU saat ini, input / output, khususnya membuka banyak file kecil , adalah salah satu hambatan utama.
Damon
1
"Hari ini sekolah tua termasuk penjaga secepat #pragma sekali." Hari ini, dan juga bertahun-tahun yang lalu. Dokumen tertua di situs GCC adalah untuk 2,95 dari tahun 2001 dan mengoptimalkan menyertakan penjaga bukanlah hal baru. Ini bukan optimasi terbaru.
Jonathan Wakely
4
Manfaat utama adalah bahwa penjaga termasuk rawan kesalahan dan bertele-tele. Terlalu mudah untuk memiliki dua file berbeda dengan nama yang identik di direktori yang berbeda (dan pelindung yang disertakan mungkin simbol yang sama), atau membuat kesalahan salin-tempel saat menyalin termasuk pelindung. Pragma dulu lebih rentan kesalahan, dan bekerja pada semua platform PC utama. Jika Anda bisa menggunakannya, itu adalah gaya yang lebih baik.
Membantu
2

Perbedaan utama adalah bahwa kompiler harus membuka file header untuk membaca guard include. Sebagai perbandingan, pragma pernah menyebabkan kompiler untuk melacak file dan tidak melakukan file IO ketika menemukan file lain termasuk untuk file yang sama. Meskipun itu kedengarannya dapat diabaikan, ia dapat dengan mudah ditingkatkan dengan proyek-proyek besar, terutama yang tanpa header yang baik termasuk disiplin ilmu.

Yang mengatakan, hari ini kompiler (termasuk GCC) cukup pintar untuk memperlakukan termasuk penjaga seperti pragma sekali. yaitu mereka tidak membuka file dan menghindari hukuman file IO.

Dalam kompiler yang tidak mendukung pragma saya telah melihat implementasi manual yang sedikit rumit ..

#ifdef FOO_H
#include "foo.h"
#endif

Saya pribadi suka pendekatan #pragma sekali karena menghindari kerumitan penamaan tabrakan dan kesalahan ketik potensial. Ini juga kode yang lebih elegan dengan perbandingan. Yang mengatakan, untuk kode portabel, tidak ada salahnya untuk memiliki keduanya kecuali jika kompiler mengeluh tentang hal itu.

Shammi
sumber
1
"Yang mengatakan, hari ini kompiler (termasuk GCC) cukup pintar untuk memperlakukan termasuk penjaga seperti pragma sekali." Mereka telah melakukannya selama beberapa dekade, mungkin lebih lama dari yang #pragma oncesudah ada!
Jonathan Wakely
Pikir Anda salah paham dengan saya. Saya bermaksud mengatakan sebelum pragma sekali, semua penyusun akan dikenakan beberapa IO hukuman untuk file h yang sama termasuk beberapa kali selama tahap preprocessor. Implementasi modern akhirnya menggunakan caching file yang lebih baik pada tahap preprocessor. Apapun, tanpa pragma, tahap preprocessor berakhir masih termasuk semua yang di luar penjaga termasuk. Dengan pragma sekali, seluruh file ditinggalkan. Dari sudut pandang itu, pragma masih menguntungkan.
Shammi
1
Tidak, itu salah, kompiler yang layak membiarkan seluruh file keluar bahkan tanpa #pragma sekali, mereka tidak membuka file untuk kedua kalinya dan mereka bahkan tidak melihatnya untuk kedua kalinya, lihat gcc.gnu.org/onlinedocs/ cpp / Once-Only-Headers.html (ini tidak ada hubungannya dengan caching).
Jonathan Wakely
1
Dari tautan Anda, sepertinya optimasi hanya terjadi di cpp. Apapun, caching memang ikut berperan. Bagaimana preprocessor tahu untuk memasukkan kode di luar penjaga. Contoh ... extern int foo; #ifndef INC_GUARD #define INC_GUARD class ClassInHeader {}; # endif Dalam hal ini preprocessor harus menyertakan extern int foo; beberapa kali jika Anda memasukkan file yang sama beberapa kali. Akhir hari, tidak banyak gunanya memperdebatkan hal ini selama kita memahami perbedaan antara #pragma sekali dan termasuk penjaga dan bagaimana berbagai penyusun berperilaku dengan mereka berdua :)
Shammi
1
Itu tidak berlaku optimasi itu, jelas.
Jonathan Wakely
1

Jika kita menggunakan msvc atau Qt (hingga Qt 4.5), karena GCC (hingga 3.4), msvc keduanya mendukung #pragma once, saya tidak dapat melihat alasan untuk tidak menggunakan #pragma once.

Nama file sumber biasanya sama dengan nama kelas, dan kami tahu, kadang-kadang kami perlu refactor , untuk mengganti nama kelas, maka kami harus mengubah #include XXXXjuga, jadi saya pikir pemeliharaan manual #include xxxxxbukanlah pekerjaan yang cerdas. bahkan dengan ekstensi Visual Assist X, mempertahankan "xxxx" bukanlah pekerjaan yang diperlukan.

raidsan
sumber
1

Catatan tambahan untuk orang-orang yang berpikir bahwa pemasukan file header secara otomatis satu kali saja selalu diinginkan: Saya membuat generator kode menggunakan inklusi file header ganda atau ganda sejak beberapa dekade. Khusus untuk pembuatan rintisan pustaka protokol saya merasa sangat nyaman untuk memiliki generator kode yang sangat portabel dan kuat tanpa alat dan bahasa tambahan. Saya bukan satu-satunya pengembang yang menggunakan skema ini karena blog ini menunjukkan X-Macros . Ini tidak akan mungkin dilakukan tanpa penjagaan otomatis yang hilang.

Marcel
sumber
Bisakah templat C ++ menyelesaikan masalah? Saya jarang menemukan kebutuhan yang valid untuk makro karena bagaimana template C ++.
jelas
1
Pengalaman profesional jangka panjang kami adalah bahwa menggunakan bahasa yang matang, perangkat lunak, dan infrastruktur alat sepanjang waktu memberi kami sebagai penyedia layanan (Sistem Tertanam) keuntungan besar dalam produktivitas dan fleksibilitas. Pesaing yang mengembangkan perangkat lunak dan sistem tertanam berbasiskan C ++ mungkin menemukan beberapa pengembang mereka lebih senang bekerja. Tetapi kami biasanya mengungguli mereka dari waktu ke pasar, fungsionalitas dan fleksibilitas beberapa kali. Nether meremehkan keuntungan produktivitas dari menggunakan satu dan alat yang sama berulang-ulang. Web-Devs malah menderita dari cara ke banyak Kerangka.
Marcel
Catatan: tidak termasuk penjaga / # pragma sekali dalam setiap file header terhadap prinsip KERING itu sendiri. Saya dapat melihat poin Anda dalam X-Macrofitur ini, tetapi ini bukan penggunaan utama dari include, bukankah harus sebaliknya seperti header salut / # pragma multi jika kita ingin tetap KERING?
caiohamamura
KERING adalah singkatan dari "Dont repeat YourSELF". Ini tentang manusia. Apa yang dilakukan mesin, tidak ada hubungannya dengan paradigma itu. Templat C ++ berulang kali banyak, kompiler-C melakukan hal yang sama (mis. Loop unrolling) dan setiap komputer mengulangi hampir semua yang seringkali sulit dipercaya dan cepat.
Marcel