C ++ şiddetle yazılmış typedef


49

Derleme aşamasında belirli bir hata sınıfı yakalayabilmek için, güçlü bir şekilde yazılmış typedefleri bildirmenin bir yolunu düşünmeye çalışıyorum. Bu, çoğu zaman çeşitli kimlik türlerine bir int veya tanımlayacağım veya konum veya hız için bir vektör yazacağım durum budur:

typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;

Bu, kodun amacını daha net bir hale getirebilir, ancak uzun bir kodlama gecesinden sonra, farklı türdeki kimlikleri karşılaştırmak veya belki de bir hıza pozisyon eklemek gibi aptalca hatalar yapabilir.

EntityID eID;
ModelID mID;

if ( eID == mID ) // <- Compiler sees nothing wrong
{ /*bug*/ }


Position p;
Velocity v;

Position newP = p + v; // bug, meant p + v*s but compiler sees nothing wrong

Maalesef, güçlü bir şekilde yazılmış typedef'ler için bulduğum öneriler, en azından benim için olasılık olmayan bir destek kullanıyor (en azından c ++ 11 var). Bu yüzden biraz düşündükten sonra, bu fikre ulaştım ve bir başkası tarafından çalıştırmak istedim.

İlk olarak, taban türünü şablon olarak bildirirsiniz. Ancak template parametresi tanımdaki hiçbir şey için kullanılmaz:

template < typename T >
class IDType
{
    unsigned int m_id;

    public:
        IDType( unsigned int const& i_id ): m_id {i_id} {};
        friend bool operator==<T>( IDType<T> const& i_lhs, IDType<T> const& i_rhs );
};

Arkadaş işlevlerinin gerçekte, şablon tanımının ileri bir bildirimini gerektiren sınıf tanımından önce bildirilmesi gerekir.

Daha sonra tüm üyeleri sadece bir şablon sınıfı olduğunu hatırlatarak temel tip için tanımladık.

Son olarak, kullanmak istediğimizde, onu şöyle tanımlarız:

class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;

Türler şimdi tamamen ayrı. EntityID alan işlevler, örneğin bunun yerine bir ModelID beslemeye çalışırsanız derleyici hatası verir. Temel türleri şablon olarak ilan etmek zorunda kalmanın yanı sıra, gerektirdiği konularla birlikte oldukça kompakt bir yapıya sahiptir.

Birisinin bu fikir hakkında yorum veya eleştirisi olmasını umuyordum.

Bunu yazarken aklıma gelen bir konu, örneğin konumlar ve hızlar durumunda, türler arasında eskisi kadar özgürce dönemediğimdir. Bir vektörü skaler ile çarpmadan önce başka bir vektör verirse, ben de yapabilirdim:

typedef float Time;
typedef Vector3 Position;
typedef Vector3 Velocity;

Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };

Position newP = p + v*t;

Güçlü yazılmış typedef'imle derleyiciye, bir Hızın bir Zamana göre çarpılmasının bir Pozisyonla sonuçlandığını söylemek zorundayım.

class TimeT;
typedef Float<TimeT> Time;
class PositionT;
typedef Vector3<PositionT> Position;
class VelocityT;
typedef Vector3<VelocityT> Velocity;

Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };

Position newP = p + v*t; // Compiler error

Bunu çözmek için, her türlü dönüşümü açıkça uzmanlaşmam gerektiğini düşünüyorum, bu biraz rahatsız edici olabilir. Öte yandan, bu sınırlama diğer hata türlerini önlemeye yardımcı olabilir (diyelim ki, Hızı bir Mesafe ile çarpmak, belki de bu alanda anlamlı olmaz). Bu yüzden paramparça oluyorum ve insanların asıl meselem hakkında herhangi bir fikrinin ya da çözme yaklaşımım hakkında herhangi bir fikri olup olmadığını merak ediyorum.



Yanıtlar:


39

Bunlar, hayali tür parametreleri , yani gösterimleri için değil, aynı gösterime sahip türlerin farklı “boşluklarını” ayırmak için kullanılan parametreli bir tür parametreleridir.

Ve boşluklardan bahsedersek, bu hayalet türlerin kullanışlı bir uygulamasıdır:

template<typename Space>
struct Point { double x, y; };

struct WorldSpace;
struct ScreenSpace;

// Conversions between coordinate spaces are explicit.
Point<ScreenSpace> project(Point<WorldSpace> p, const Camera& c) {  }

Gördüğünüz gibi, birim tiplerinde bazı zorluklar var. Yapabileceğiniz şeylerden biri, birimleri, temel bileşenler üzerindeki bir tamsayı üsleri vektörüne ayırmaktır:

template<typename T, int Meters, int Seconds>
struct Unit {
  Unit(const T& value) : value(value) {}
  T value;
};

template<typename T, int MA, int MB, int SA, int SB>
Unit<T, MA - MB, SA - SB>
operator/(const Unit<T, MA, SA>& a, const Unit<T, MB, SB>& b) {
  return a.value / b.value;
}

Unit<double, 0, 0> one(1);
Unit<double, 1, 0> one_meter(1);
Unit<double, 0, 1> one_second(1);

// Unit<double, 1, -1>
auto one_meter_per_second = one_meter / one_second;

Burada çalışma zamanı değerlerini, ilgili birimlerdeki üslerle ilgili derleme zamanı bilgileriyle etiketlemek için hayalet değerleri kullanıyoruz . Bu, hızlar, mesafeler ve benzerleri için ayrı yapılar yapmaktan daha iyi ölçeklenir ve kullanım durumunuzu örtmek için yeterli olabilir.


2
Hmm, şablon sistemini operasyonlara zorlamak için kullanmak çok güzel. Düşünmemiştim, teşekkürler! Şimdi, örneğin, metre ile kilometre arasındaki dönüşüm gibi şeyleri zorlayabilir misiniz?
Kian

@Kian: SI baz ünitelerini dahili olarak kullanırsınız (m, kg, s, A, & c.) ve kolaylık olması için 1km = 1000m takma adını tanımlayın.
Jon Purdy

7

Bazı tam sayı değerlerinin farklı anlamlarını ayırt etmek ve aralarında örtük dönüşümleri yasaklamak istediğim benzer bir durum vardı. Bunun gibi genel bir sınıf yazdım:

template <typename T, typename Meaning>
struct Explicit
{
  //! Default constructor does not initialize the value.
  Explicit()
  { }

  //! Construction from a fundamental value.
  Explicit(T value)
    : value(value)
  { }

  //! Implicit conversion back to the fundamental data type.
  inline operator T () const { return value; }

  //! The actual fundamental value.
  T value;
};

Tabii ki daha da güvenli olmasını istiyorsanız, yapabilirsiniz Tyapıcısı explicitde. MeaningSonra böyle kullanılır:

typedef Explicit<int, struct EntityIDTag> EntityID;
typedef Explicit<int, struct ModelIDTag> ModelID;

1
Bu ilginç, ama yeterince güçlü olduğundan emin değilim. Eğer typedefed tipli bir fonksiyon ilan edersem, sadece doğru elemanların parametre olarak kullanılabileceğini garanti eder, ki bu iyi bir parametredir. Ancak diğer her kullanım için parametrelerin karışmasını engellemeden sözdizimsel ek yük ekler. Karşılaştırma gibi işlemleri söyler. operatörü == (int, int) bir EntityID ve bir ModelID'yi şikayet etmeden alacak (açıkça belirtmemi zorunlu olsa bile, beni yanlış değişkenleri kullanmamı engellemiyor).
Kian

Evet. Benim durumumda kendime farklı türden kimlikler vermemeliydim. Karşılaştırmalar ve aritmetik işlemler benim için önemli değil. Yukarıdaki yapı atamayı yasaklar, ancak diğer işlemleri yasaklar.
mindriot

Buna daha fazla enerji koymak için istekli iseniz, Explicit sınıfını en yaygın operatörleri sayarak, operatörleri de idare eden (oldukça) genel bir versiyon oluşturabilirsiniz. Bir örnek için pastebin.com/FQDuAXdu adresine bakın - sarmalayıcı sınıfının gerçekten sarılmış operatörleri sağlayıp sağlamadığını belirlemek için oldukça karmaşık bazı SFINAE yapılarına ihtiyacınız var ( bu SO sorusuna bakın ). Dikkat et, hala tüm vakaları kapsayamıyor ve belaya değmeyebilir.
mindriot

Sözdizimsel olarak zarif olsa da, bu çözüm tamsayı türleri için önemli bir performans cezası doğuracaktır. Tamsayılar yazmaçlardan geçirilebilir, yapılar (tek bir tamsayı içeren bile) yapılamaz.
Ghostrider

1

Aşağıdakilerin üretim kodunda nasıl çalıştığından emin değilim (CS101 acemi gibi bir C ++ / programlama acemi yaşıyorum), ancak bunu C ++ 'ın makro sistemlerini kullanarak hazırladım.

#define newtype(type_, type_alias) struct type_alias { \

/* make a new struct type with one value field
of a specified type (could be another struct with appropriate `=` operator*/

    type_ inner_public_field_thing; \  // the masked_value
    \
    explicit type_alias( type_ new_value ) { \  // the casting through a constructor
    // not sure how this'll work when casting non-const values
    // (like `type_alias(variable)` as opposed to `type_alias(bare_value)`
        inner_public_field_thing = new_value; } }

Not: Lütfen aklınıza gelebilecek tuzakları / geliştirmeleri bildirin.
Noein

1
Asıl sorudaki örneklerde olduğu gibi, bu makronun nasıl kullanıldığını gösteren bir kod ekleyebilir misiniz? Eğer öyleyse, bu harika bir cevap.
Jay Elston
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.