替换枚举以最大化编译时间检查的最佳方法

时间:2017-07-30 09:54:35

标签: c++ c++17

我有一个相当古老的应用程序处理不同种类的货币。目前,货币存储在枚举中,例如:

enum CURRENCY {
  EUR,
  USD,
  CNY
};

double convertMoney(CURRENCY in, CURRENCY out, double money_in) {
   ...
}

这是伟大的,除了这不是真正的类型安全:我还有其他函数包含 WARNING: all inputs should have the same currency 等注释。我的目标是在可能的情况下通过编译时检查来替换大多数这些注释。我可以使用C ++ 17和boost。

我想过使用std::variant这样:

class EUR {};
class USD {};
class CNY {};

using CURRENCY = std::variant<EUR,USD,CNY>;

template<typename IN, typename OUT>
class Market {
 public:
   ...
   double convertMoney(double in) {
      return in*rate;
   }
 private:
   void updateRate() {
      ....
      rate = some_value_fetched_at_runtime;
   }
   double rate;
};

int main() {
    Market<EUR, USD> eur_usd;
    Market<EUR, CNY> eur_cny;

    std::vector<Market<CURRENCY,CURRENCY>> all_markets{eur_usd, eur_cny};
    ...
    //do something 
    ...
    return 0;
}

但是,当然,由于我试图将不同类型的Market对象推送到我的向量中,所以这不会起作用。

总而言之,您认为在现代C ++中替换枚举的最佳方法是什么?如果使用std::variant是正确的,那么解决上述问题的最佳方法是什么?

请注意:

  1. 我可以做using Markets = std::variant<Market<EUR,USD>,Market<EUR,CNY>,...>这样的事情,但这在实践中并不可行,因为我有大约100种市场,这并没有真正提高可维护性。
  2. 我可以创建一个类CURRENCY并拥有EURUSDCNY子类CURRENCY,但这会在运行时使用v-table这会降低编译时间检查的次数。如果有人可以证明我的相反,我会接受它。
  3. 我的所有市场都在我的源代码中定义(我不需要在运行时动态创建一种新的市场),但速度正在动态变化。

2 个答案:

答案 0 :(得分:5)

  1. 您可以将每种货币作为单独的(模板)类,并在它们之间进行显式转换,就像它们在:: std :: chrono或:: boost :: unit中一样。

    template< CURRENCY VCurrencyId > class
    t_Sum
    {
        public: using
        t_Value = double;
    
        private: t_Value m_value{};
    
        public: 
        t_Sum(void)
        {}
    
        public: 
        t_Sum(t_Sum const & other)
        :   m_value{other.m_value}
        {}
    
        public: explicit 
        t_Sum(t_Value const & value)
        :   m_value{value}
        {}
    
        public: t_Sum &
        operator =(t_Sum const & other)
        {
            m_value = other.m_value;
            return(*this);
        }
    
        public: t_Value const &
        Get_Value(void)
        {
            return(m_value);
        }
    
        public: void
        Set_Value(t_Value const & value)
        {
            m_value = value;
        }
    };
    
    template< CURRENCY VInputCurrencyId, CURRENCY VOutputCurrencyId > t_Sum< VOutputCurrencyId >
    Convert(t_Sum< VInputCurrencyId > in) {
        ...
    }
    
    using
    t_Eur = t_Sum< EUR >;
    
    using
    t_Usd = t_Sum< USD >;
    
    t_Eur euros{};
    t_Usd bucks{euros};
    // compile-time error, conversion required!
    // or you can add converting constructor
    

答案 1 :(得分:0)

You may do something like:

enum class CURRENCY {
    EUR = 0,
    USD,
    CNY,
    LAST = CNY     
};

template <CURRENCY cur> class Price;

// Class to handle convertion
class Market
{
public:
    template <CURRENCY Out, CURRENCY In>
    Price<Out> convertMoney(const Price<In>&);

    void updateChange(CURRENCY in, CURRENCY out, double ratio);
private:
    double ratios[int(CURRENCY::Last)][int(CURRENCY::Last)];
};

// Class to represent typed currency value
template <CURRENCY cur>
class Price
{
public:
    explicit Price(double value) : value(value) {}

    // should only be used when we want the value without the type
    double getValue() const { return value; }

    // operation which restrict same currency as
    Price& operator +=(const Price&);
    // ...

private:
    double value;
};