Constexpr ve makrolar


92

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;

4
AFAIK constexpr daha fazla tip güvenliği sağlıyor
Code-Apprentice

13
Kolay: constexr, her zaman.
n. zamirler 'm.

Sorularınızdan bazılarına cevap verebilir stackoverflow.com/q/4748083/540286
Ortwin Angermeier

Yanıtlar:


146

Temelde aynı değiller mi?

Kesinlikle değil. Yakınında bile değil.

Aslında dışında makro bir olduğunu intve senin constexpr unsignedbir olduğunu unsignedsadece sahip önemli farklılıklar ve makrolar vardır, bir avantaj.

Dürbün

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 #undefyeniden 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?

Gerçek bir hafıza konumu

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_HEIGHTbir değişken olmamasıdır, bu nedenle std::maxgeçici çağrı için intderleyici tarafından oluşturulması gerekir. Tarafından döndürülen referans std::maxdaha sonra bu ifadenin bitiminden sonra var olmayan geçici olana başvurabilir, bu nedenle return hgeç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& hancak sorun daha ince bağlamlarda ortaya çıkabilir.)

Önişlemci koşulları

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 #( #includeve #defineve 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>;

3
Bir constexprdeğ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 .
underscore_d

3
"Gerçek bellek konumu" bölümünüz yanlış: 1. Değer (int) ile geri dönüyorsunuz, bu nedenle bir kopya yapıldı, geçici bir sorun değil. 2. Eğer referans (int &) ile döndüyseniz, o zaman int heightmakronun 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.
PoweredByRice

4
@underscore_d true, ancak bu argümanı değiştirmez. Değişken, bir odr kullanımı olmadıkça depolama gerektirmez. Önemli olan, depolamalı gerçek bir değişken gerektiğinde, constexpr değişkeninin doğru olanı yapmasıdır.
Jonathan Wakely

1
@PoweredByRice 1. problemin dönüş değeriyle ilgisi yok 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.
Jonathan Wakely

3
@PoweredByRice iç çekiyor, C ++ 'ın bana nasıl çalıştığını açıklamanıza gerçekten gerek yok. Eğer sahipseniz const int& h = max(x, y);ve maxdeğ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.
Jonathan Wakely

11

Genel olarak konuşursak, mümkün olduğunca kullanmalısınız constexprve makroları yalnızca başka bir çözüm mümkün değilse kullanmalısınız.

Gerekçe:

Makrolar kodda basit bir yer değiştirmedir ve bu nedenle genellikle çakışmalara neden olurlar (örneğin windows.h maxmakro 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_PROPERTYyapı ü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.


1
Hangi durumda makro kullanmak kaçınılmazdır?
Tom Dorone

3
İe kullanan koşullu derleme #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.
Jonathan Wakely

Değişken makrolar haricinde, çoğunlukla makro derleyici anahtarları için kullanılır, ancak mevcut makro ifadelerini (koşullu, dizgi değişmez anahtarları gibi) gerçek kod ifadeleriyle birlikte constexpr ile değiştirmeye çalışmak iyi bir fikir mi?

Derleyici anahtarlarının da iyi bir fikir olmadığını söyleyebilirim. Bununla birlikte, özellikle çapraz platform veya gömülü kodla uğraşırken, bazen (ayrıca makrolar) gerekli olduğunu tamamen anlıyorum. Sorunuzu cevaplamak için: Eğer önişlemci ile zaten uğraşıyorsanız, önişlemcinin ne olduğunu ve derleme zamanının ne olduğunu açık ve sezgisel tutmak için makroları kullanırdım. Ayrıca yoğun bir şekilde yorum yapmanızı ve mümkün olduğunca kısa ve yerel olarak kullanılmasını öneririm (makroların etrafına veya 100 satıra yayılmasını önleyin #if). Belki de istisna, iyi anlaşılan tipik #ifndef korumadır (#pragma için bir kez standarttır).
Adrian Maire

3

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

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 Triggerve ekleme şeyler gibi /D:Trigger, -DTriggerkullanı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:

  1. Donanıma ve Platforma özel kod bölümleri
  2. Daha fazla ayrıntı yapıları

Dolayısıyla, OP'nin durumunda bir int'i constexprveya 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.