可信变量






4.70/5 (24投票s)
一种简单而优雅的创建“可信”数据变量的方法。
引言
“可信变量”旨在简化三种非常常见的编码方法
- 设置变量默认值。
- 定义可以分配给变量的有效值的范围。
- 验证赋值(分配的值在范围内)对于任何赋值。
通常,这三种方法在不同的地方实现:默认值在声明或初始化时设置,范围定义仅是语义上的,并且赋值验证是针对任何特定赋值实现的。
“可信变量”试图将这三种方法集中到一个地方 - 声明。一旦声明了“可信变量”,就会设置默认值,并且任何尝试分配“不在范围内”的值都将失败。
示例
假设我们有一些应用程序,它使用日历日作为变量..
理想情况下,我们希望定义一些“可信”的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
,并且可能是hour
,minute
和second
。我假设它们都将具有相同的错误处理方法。
在每个可信变量的定义中提供错误处理方法作为虚拟方法,将添加另一个参数,更重要的是,将添加内存复杂性(将为每个单个变量创建虚拟表)和性能复杂性(错误处理方法将在运行时解析)。
我建议使用一种编译时“命名空间多态性”来代替它。
让我们定义一个错误处理方法,范围由特定命名空间确定。
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支持)上测试
结论
使用它们!:)