Makroları nerede kullanmayı tercih etmeliyim ve nerede constexpr'i tercih etmeliyim ? Temelde aynı değiller mi?
#define MAX_HEIGHT 720
vs
constexpr unsigned int max_height = 720;
Makroları nerede kullanmayı tercih etmeliyim ve nerede constexpr'i tercih etmeliyim ? Temelde aynı değiller mi?
#define MAX_HEIGHT 720
vs
constexpr unsigned int max_height = 720;
Yanıtlar:
Temelde aynı değiller mi?
Kesinlikle değil. Yakınında bile değil.
Aslında dışında makro bir olduğunu int
ve senin constexpr unsigned
bir olduğunu unsigned
sadece sahip önemli farklılıklar ve makrolar vardır, bir avantaj.
Bir makro, önişlemci tarafından tanımlanır ve her oluştuğunda koda basitçe ikame edilir. Önişlemci aptaldır ve C ++ sözdizimini veya anlambilimini anlamaz. Makrolar, ad alanları, sınıflar veya işlev blokları gibi kapsamları göz ardı eder, bu nedenle bir kaynak dosyada başka hiçbir şey için ad kullanamazsınız. Bu, uygun bir C ++ değişkeni olarak tanımlanan bir sabit için doğru değildir:
#define MAX_HEIGHT 720
constexpr int max_height = 720;
class Window {
// ...
int max_height;
};
Bir üye değişkeninin çağrılmasının bir sakıncası yoktur max_height
çünkü bir sınıf üyesi olduğu için farklı bir kapsamı vardır ve ad alanı kapsamındakinden farklıdır. MAX_HEIGHT
Üyenin adını yeniden kullanmayı denediyseniz , önişlemci bunu derlemeyecek bu saçmalıkla değiştirir:
class Window {
// ...
int 720;
};
Bu nedenle, UGLY_SHOUTY_NAMES
öne çıkmalarını sağlamak için makrolar vermelisiniz ve çatışmaları önlemek için bunları adlandırırken dikkatli olabilirsiniz. Makroları gereksiz yere kullanmazsanız, bunun için endişelenmenize gerek yoktur (ve okumak zorunda değilsiniz SHOUTY_NAMES
).
Bir fonksiyonun içinde bir sabit istiyorsanız, bunu bir makro ile yapamazsınız, çünkü önişlemci bir fonksiyonun ne olduğunu veya içinde olmanın ne anlama geldiğini bilmez. Bir makroyu dosyanın yalnızca belirli bir kısmıyla sınırlamak için, makroyu #undef
yeniden yapmanız gerekir :
int limit(int height) {
#define MAX_HEIGHT 720
return std::max(height, MAX_HEIGHT);
#undef MAX_HEIGHT
}
Çok daha mantıklı olanla karşılaştırın:
int limit(int height) {
constexpr int max_height = 720;
return std::max(height, max_height);
}
Neden makro olanı tercih edersiniz?
Bir constexpr değişkeni bir değişkendir, bu nedenle programda gerçekten bulunur ve adresini alıp ona bir başvuru bağlamak gibi normal C ++ şeyleri yapabilirsiniz.
Bu kodun tanımlanmamış davranışı var:
#define MAX_HEIGHT 720
int limit(int height) {
const int& h = std::max(height, MAX_HEIGHT);
// ...
return h;
}
Sorun, bunun MAX_HEIGHT
bir değişken olmamasıdır, bu nedenle std::max
geçici çağrı için int
derleyici tarafından oluşturulması gerekir. Tarafından döndürülen referans std::max
daha sonra bu ifadenin bitiminden sonra var olmayan geçici olana başvurabilir, bu nedenle return h
geçersiz belleğe erişir.
Bu problem basitçe uygun bir değişkenle mevcut değildir, çünkü hafızada kaybolmayan sabit bir konuma sahiptir:
int limit(int height) {
constexpr int max_height = 720;
const int& h = std::max(height, max_height);
// ...
return h;
}
(Pratikte muhtemelen söylemeyeceksiniz int h
, const int& h
ancak sorun daha ince bağlamlarda ortaya çıkabilir.)
Bir makroyu tercih etmenin tek zamanı, değerinin önişlemci tarafından anlaşılması gerektiği zamandır #if
, örneğin koşullarda kullanım için
#define MAX_HEIGHT 720
#if MAX_HEIGHT < 256
using height_type = unsigned char;
#else
using height_type = unsigned int;
#endif
Burada bir değişken kullanamazsınız çünkü önişlemci değişkenlere isme göre nasıl atıfta bulunulacağını anlamaz. Yalnızca makro genişleme ve #
( #include
ve #define
ve gibi #if
) ile başlayan direktifler gibi temel çok temel şeyleri anlar .
Önişlemci tarafından anlaşılabilecek bir sabit istiyorsanız, onu tanımlamak için önişlemciyi kullanmalısınız. Normal C ++ kodu için bir sabit istiyorsanız, normal C ++ kodunu kullanın.
Yukarıdaki örnek sadece bir önişlemci koşulunu göstermek içindir, ancak bu kod bile önişlemciyi kullanmaktan kaçınabilir:
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
constexpr
değişkenin adresi (bir işaretçi / başvuru) alınana kadar hafızayı işgal etmesi gerekmez; aksi takdirde tamamen optimize edilebilir (ve bunu garanti eden Standartlar olabileceğini düşünüyorum). Bunu vurgulamak istiyorum, böylece insanlar, depolamayı gerektirmeyen enum
önemsiz bir constexpr
şeyin yine de bazılarını işgal edeceğine dair yanlış yönlendirilmiş bir fikirden eski, aşağı ' hack'i kullanmaya devam etmezler .
int height
makronun kapsamı da işleve bağlı olduğu için, makronunki kadar problem olur, esasen geçici de olur. 3. Yukarıdaki "const int & h, geçicinin ömrünü uzatacak" yorumu doğrudur.
limit
, problemin dönüş değeri std::max
. 2. evet, bu yüzden bir referans döndürmüyor. 3. yanlış, yukarıdaki coliru bağlantısına bakın.
const int& h = max(x, y);
ve max
değere göre geri dönüyorsa, dönüş değerinin ömrü uzatılır. Dönüş türüne göre değil, const int&
buna bağlıdır. Yazdıklarım doğrudur.
Genel olarak konuşursak, mümkün olduğunca kullanmalısınız constexpr
ve makroları yalnızca başka bir çözüm mümkün değilse kullanmalısınız.
Makrolar kodda basit bir yer değiştirmedir ve bu nedenle genellikle çakışmalara neden olurlar (örneğin windows.h max
makro vs std::max
). Ek olarak, çalışan bir makro kolaylıkla farklı bir şekilde kullanılabilir ve bu da daha sonra garip derleme hatalarını tetikleyebilir. (örneğin Q_PROPERTY
yapı üyelerinde kullanılır)
Tüm bu belirsizlikler nedeniyle, makrolardan kaçınmak iyi bir kod stilidir, tıpkı genellikle gotoslardan kaçındığınız gibi.
constexpr
anlamsal olarak tanımlanmıştır ve bu nedenle tipik olarak çok daha az sorun üretir.
#if
, önişlemcinin aslında yararlı olduğu şeylerdir. Bir sabitin tanımlanması, önişlemcinin yararlı olduğu şeylerden biri değildir, bu sabit bir makro olması gerekmedikçe, çünkü önişlemci koşullarında kullanılır #if
. Sabit normal C ++ kodunda kullanım içinse (önişlemci yönergeleri değil), o zaman bir önişlemci makrosu değil, normal bir C ++ değişkeni kullanın.
Jonathon Wakely'den harika cevap . Makroların kullanımını düşünmeye başlamadan önce ve aradaki farkın ne olduğuna dair jogojapan'ın cevabına bir göz atmanızı da tavsiye ederim .const
constexpr
Makrolar aptaldır, ancak iyi anlamda . Görünüşte bugünlerde, kodunuzun çok özel bölümlerinin yalnızca belirli yapı parametrelerinin "tanımlanmış" olması durumunda derlenmesini istediğinizde bir yapı yardımcısıdırlar. Genellikle araçlar daha iyisi Makro adını veya alıyor bütün bu, hadi bir diyelim Trigger
ve ekleme şeyler gibi /D:Trigger
, -DTrigger
kullanılan yapı araçlarına vb.
Makrolar için birçok farklı kullanım olsa da, bunlar en sık gördüğüm, kötü / tarihi geçmiş uygulamalar değildir:
Dolayısıyla, OP'nin durumunda bir int'i constexpr
veya a ile aynı hedefi gerçekleştirebilseniz de MACRO
, modern kuralları kullanırken ikisinin çakışması pek olası değildir. İşte henüz aşamalı olarak kaldırılmamış bazı yaygın makro kullanımları.
#if defined VERBOSE || defined DEBUG || defined MSG_ALL
// Verbose message-handling code here
#endif
Makro kullanım için başka bir örnek olarak, piyasaya sürülecek bazı donanımlarınız olduğunu veya diğerlerinin gerektirmediği bazı zorlu geçici çözümlere sahip belirli bir nesliniz olduğunu varsayalım. Bu makroyu olarak tanımlayacağız GEN_3_HW
.
#if defined GEN_3_HW && defined _WIN64
// Windows-only special handling for 64-bit upcoming hardware
#elif defined GEN_3_HW && defined __APPLE__
// Special handling for macs on the new hardware
#elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__
// Greetings, Outlander! ;)
#else
// Generic handling
#endif