2016-09-09 4 views
3

Скажем, у меня есть класс, представляющий автоматы, состояния которых пронумерованы (using state_t = unsigned) и чьи транзитоны также пронумерованы (using transition_t = unsigned). Конечно, в какой-то момент я в конечном итоге messing некоторые звонки, потому что transition_t и state_t одного типа, поэтому компилятор не обеспечивает безопасность (семантического) типа. Это легко обойти, используя небольшой класс с шаблоном (struct transition_tag {}; struct state_tag {};), так что теперь transition_t и state_t несовместимы, хорошо!Семантические типы, связанные с компилятором

/// Lightweight state/transition handle (or index). 
template <typename Tag> 
struct index_t_impl 
{ 
    using index_t = unsigned; 

    constexpr index_t_impl(index_t i) 
    : s{i} 
    {} 

    // Disallow index1_t i{index2_t{42}}; 
    template <typename T> 
    index_t_impl(index_t_impl<T> t) = delete; 

    bool operator==(index_t_impl t) const 
    { 
    return s == t.s; 
    } 

    // Disallow index1_t{42} == index2_t{42}; 
    template <typename T> 
    bool operator==(index_t_impl<T> t) const = delete; 

    /// Default ctor to please containers. 
    index_t_impl() = default; 
    constexpr operator index_t() const { return s; } 
    /// Be compliant with Boost integer ranges. 
    index_t_impl& operator++() { ++s; return *this; } 
    /// Be compliant with Boost integer ranges. 
    index_t_impl& operator--() { --s; return *this; } 

private: 
    index_t s; 
}; 

Кроме того, у меня есть две структуры, которые очень похожи друг на друга:

  • predecessors_t карты от перехода к переходу его предшественника (в кратчайшему пути). Для эффективности это std::vector<transition_t>.
  • path_t - это список индексов перехода. Для эффективности это std::vector<transition_t>.

И снова у меня есть эта проблема, что я использую std::vector<transition_t> для двух совершенно разных целей. Конечно, я мог бы снова ввести обертку с шаблоном, но потом все снова становится грязным. Публичное наследование очень заманчиво (Thou shalt not inherit from std::vector)!

Но на самом деле, я устал от ad-hoc-решений каждый раз, когда хочу вводить новые типы, которые в точности похожи на базовый тип, но просто несовместимы. Есть ли какие-либо рекомендации по этому вопросу? Публичное наследование действительно привлекательно, но разве это не приведет к раздуванию кода с тоннами при дополнительных экземплярах? Может быть, публичная композиция (struct predecessors_t { std::vector<transition_t> v; };) в соответствии с рекомендациями Crashworks (https://stackoverflow.com/a/4353276/1353549) - лучший вариант, который лучше масштабируется?

Есть ли что-нибудь в поле зрения в будущем на C++ для решения этой проблемы?

+2

Я не знаю об этом идет в стандарте, но вы можете посмотреть здесь: HTTP: // StackOverflow.com/вопросы/28916627/strong-typedefs – Hayt

ответ

6

Эта проблема получения семантических типов, связанных с компилятором, может возникать во всех ситуациях, из вашей ситуации, чтобы согласовывать системы с различным происхождением (где значения имеют одинаковый тип (например, int), но семантически , типы не должны смешиваться, поскольку они представляют собой смещения от разных истоков (x, y, z = 0,0,0) - это часто встречается в математике, где при графике квадранта с положительными x и y происхождение находится в левом нижнем углу, а компьютерная наука, где очень часто размещать начало в левом верхнем углу) до навигации на космическом корабле (подробнее об этом ниже).

В 2012 году Бьярне Страуструп дал интересный разговор о том, что он назвал тип богатого программирования введения компиляторами семантический тип безопасности с C++ 11 с помощью шаблонов, определяемых пользователем литералов, заявленное не во время выполнения накладные расходы и даже рассказы об уроках, извлеченных из наблюдателя «Наблюдение за климатом Марса» (космический корабль с космическим кораблем + 350 млн. долл. США потерял из-за отсутствия обеспеченной безопасности семантического типа). Вы можете увидеть часть разговора, где он охватывает семантические типы: https://youtu.be/0iWb_qi2-uI?t=19m6s

Я написал примерный отрывок кода на основе демонстрационного кода Stroustrup, обновленного до современных стандартов и с реализацией необходимых перегрузок операторов). В отличие от примера Бьярне, он фактически компилируется. ;)

Суть этого кода можно найти здесь: https://gist.github.com/u-007d/361221df5f8c7f3466f0f09dc96fb1ba

//Compiled with clang -std=c++14 -Weverything -Wno-c++98-compat main.cpp -o main 

#include <iostream> 
#include <string> 

template<int M, int K, int S> //Meters, Kilograms, Seconds (MKS) 
struct Unit 
{ 
    enum { m=M, kg=K, s=S }; 
}; 

template<typename Unit> //a magnitude with a unit 
struct Value 
{ 
    double val; //the magnitude 
    constexpr explicit Value(double d) : val(d) {} //construct a Value from a double 
}; 

//Basic Semantic Units for MKS domain 
using Meter = Unit<1, 0, 0>; 
using Kilogram = Unit<0, 1, 0>; 
using Second = Unit<0, 0, 1>; 
using Second2 = Unit<0, 0, 2>; 

//Semantic Value Types for MKS domain 
using Time = Value<Second>; 
using Distance = Value<Meter>; 
using Mass = Value<Kilogram>; 
using Speed = Value<Unit<1, 0, -1>>; //Speed is meters/second 
using Acceleration = Value<Unit<1, 0, -2>>; //Acceleration is meters/second^2 

//Operator overloads to properly calculate units (incomplete; for demo purposes) 
Speed operator/(const Distance& lhs, const Time& rhs) 
{ 
    return Speed(lhs.val/rhs.val); 
} 

Acceleration operator/(const Speed& lhs, const Time& rhs) 
{ 
    return Acceleration(lhs.val/rhs.val); 
} 

//Define literals 
constexpr Distance operator"" _m(long double ld) 
{ 
    return Distance(static_cast<double>(ld)); 
} 

constexpr Mass operator"" _kg(long double ld) 
{ 
    return Mass(static_cast<double>(ld)); 
} 

constexpr Time operator"" _s(long double ld) 
{ 
    return Time(static_cast<double>(ld)); 
} 

constexpr Acceleration operator"" _s2(long double ld) 
{ 
    return Acceleration(static_cast<double>(ld)); 
} 

int main() 
{ 
    Speed sp = Distance(100)/Time(9.58); //Not bad, but units could be more convenient... 
    Distance d1 = 100.0_m; //A good distance to run a race 
    Speed sp1 = 100.0_m/9.58_s; //A human can run this fast 
// Speed sp2 = 100.0_m/9.8_s2; //Error: speed is m/s, not m/s^2 
// Speed sp3 = 100.0/9.8_s; //Error: 100 has no unit 
    Acceleration ac1 = sp1/0.5_s; //Faster than any human 

    return EXIT_SUCCESS; 
} 
+1

Nice. Незначительная проблема: для 'sp3', я считаю, что она должна быть без части' _m'. –

+0

Спасибо, Yehezkel. Исправлена. – U007D

Смежные вопросы