65.9K
CodeProject 正在变化。 阅读更多。
Home

可信变量

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (24投票s)

2014 年 4 月 16 日

CPOL

3分钟阅读

viewsIcon

32656

downloadIcon

237

一种简单而优雅的创建“可信”数据变量的方法。

引言

“可信变量”旨在简化三种非常常见的编码方法

  1. 设置变量默认值。
  2. 定义可以分配给变量的有效值的范围。
  3. 验证赋值(分配的值在范围内)对于任何赋值。

通常,这三种方法在不同的地方实现:默认值在声明或初始化时设置,范围定义仅是语义上的,并且赋值验证是针对任何特定赋值实现的。

“可信变量”试图将这三种方法集中到一个地方 - 声明。一旦声明了“可信变量”,就会设置默认值,并且任何尝试分配“不在范围内”的值都将失败。

示例

假设我们有一些应用程序,它使用日历日作为变量..

理想情况下,我们希望定义一些“可信”的day,可以在没有进一步验证的情况下使用。

// variable "day" declaration, type set as uint8_t, range 1-31, and default value 15
TRUSTED_VAR( uint8_t, day, MIN(1), MAX(31), DEFAULT(15) ); 

然后,当我们使用时,我们可以将变量day引用为原生uint8_t

// May be used as a native type ( uint8_t in this case ) Lvalue in any expression
day = 10; // ok, value in range

uint8_t wrong_day = 32;
 
day = wrong_day; // out of range, error generated, "day" was not changed

// May be used as a native type ( uint8_t in this case ) Rvalue in any expression
uint8_t temp_day = day;

想法

可信变量的想法非常简单

对于赋值验证,复制构造函数和赋值运算符应涉及验证,并且对于使用“像”原生类型一样,应实现到原生类型的强制转换运算符。

实现

请注意,以下代码包含“额外”信息,例如变量名和其他“string”相关转换,这些可以省略。

#ifndef __TRUSTED_VAR__
#define __TRUSTED_VAR__
 
#include <string>
// Following constexpr functions are for decorations purposes only
template < typename T > static constexpr T MIN(T min) { return min; }
template < typename T > static constexpr T MAX(T max) { return max; }
template < typename T > static constexpr T DEFAULT(T default) { return default; }

#define TRUSTED_VAR( _type, _name, _min, _max, _default ) \
            TrustedVar< _type,_min, _max, _default > _name{#_name};
 
template 
< typename VAR_TYPE, VAR_TYPE MIN_VAL, VAR_TYPE MAX_VAL, VAR_TYPE DEFAULT_VAL > 
class TrustedVar
{
public:
    static_assert((MAX_VAL >= MIN_VAL), "Invalid Range");
 
    explicit TrustedVar(const char* _val_name):val_name{_val_name}, value{DEFAULT_VAL}
    {}
 
    TrustedVar(const TrustedVar& _var)
    {
        value = _var.value;
    }
 
    TrustedVar& operator = (const VAR_TYPE& _value)
    {
        if ( IsInRange(_value) ) value = _value;
 
        else NotInRange( val_name,
                         std::to_string(MIN_VAL), 
                         std::to_string(MAX_VAL),
                         std::to_string(_value)
                   );
 
        return *this;
    }
 
    operator VAR_TYPE()
    {
        return value;
    }
 
private:
    bool IsInRange(const VAR_TYPE& _value)
    {
        return ((MIN_VAL <= _value) && (MAX_VAL >= _value));
    }
private:
    const std::string val_name;
    VAR_TYPE value;
};
 
#endif //__TRUSTED_VAR__

对于这个特定的示例,我使用了MACRO定义TRUSTED_VAR,我想传递变量名以用于日志记录目的(_name{#_name})

如果不需要 - 则可以省略单个MACRO。

错误处理

正如我们所见,无效(超出范围)的赋值将生成错误。问题是如何处理这个错误?这实际上取决于使用可信变量的应用程序。作为一种选择,“超出范围”的赋值可能会生成异常,可能会触发“断言”,可能会生成错误日志,可能会设置最后一个错误等等。

所以理想情况下,我们希望使错误处理可定制。

关于可信变量的另一个有趣的事情是,它们通常不是“单个”出现的。在我们的变量day的示例中,很可能我们将拥有可信变量month,可信变量year,并且可能是hourminutesecond。我假设它们都将具有相同的错误处理方法。

在每个可信变量的定义中提供错误处理方法作为虚拟方法,将添加另一个参数,更重要的是,将添加内存复杂性(将为每个单个变量创建虚拟表)和性能复杂性(错误处理方法将在运行时解析)。

我建议使用一种编译时“命名空间多态性”来代替它。

让我们定义一个错误处理方法,范围由特定命名空间确定。

namespace DEFAULT_ERROR_HANDLING_NAMESPACE
{
    void NotInRange(const std::string& _var_name, 
                    const std::string& range_min, 
                    const std::string& range_max, 
                    const std::string& val)
    {
        std::cout << "ERROR: Variable \"" 
                      + std::string(_var_name) 
                      + "\":"  
                      + " value " 
                      + val 
                      + " is out of " 
                      + range_min 
                      + '-' 
                      + range_max 
                      + " range " << std::endl;
    }
}

现在,在使用可信变量之前,我们只需要声明使用包含“所需”错误处理实现的特定命名空间即可。现在,所有可信变量,当我们在“使用”这个特定命名空间时,都将由相同的方法处理(记住,可信变量不是“单独”来“参加派对”的)。

using namespace DEFAULT_ERROR_HANDLING_NAMESPACE;
 
int main()
{
    // variable "day" declaration, type set as uint8_t, range 1-31, and default value 15
    TRUSTED_VAR( uint8_t, day, MIN(1), MAX(31), DEFAULT(15) );
    
    // May be used as a native type ( uint8_t in this case ) Lvalue in any expression
    day = 10; // ok, value in range

    uint8_t wrong_day = 32;
 
    day = wrong_day; // out of range, error generated, "day" was not changed

    // May be used as a native type ( uint8_t in this case ) Rvalue in any expression
    uint8_t temp_day = day;
 
    return 0;
}

因此,对于超出范围的赋值,

ERROR: Variable "day": value 32 is out of 1-31 range 

将被生成。

自定义

请将可信变量的实现视为一个概念。通过省略不必要的信息(例如变量名,删除与string相关的操作)和/或更改NotInRange错误处理签名,可以轻松地自定义代码。

我更喜欢避免运行时多态性。

修订

  • 2014年4月27日 - 添加了constexpr函数来代替装饰宏(感谢Stefan_Lang的想法)

兼容性

  • 在VS2013 + Visual C++ Compiler November 2013 CTP (用于constexpr支持)上测试

结论

使用它们!:)

© . All rights reserved.