C ++ 11中的对象验证逻辑模式 [英] Pattern for Object Validation Logic in C++11

查看:87
本文介绍了C ++ 11中的对象验证逻辑模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想确保对象的状态始终有效。

I want to ensure that the state of an object is always valid.

让我们假设一个带有构造函数和setter的类:

Let us assume a class with a constructor and a setter:

class MyClass {
  double x;  // Must be in [0;500].

public:

  MyClass(double _x) : x(_x) {
    if (x < 0.0)
      throw /*...*/;
    if (x > 500.0)
      throw /*...*/;
  }

  void SetX(double _x) {
    x = _x;
    if (x < 0.0)
      throw /*...*/;
    if (x > 500.0)
      throw /*...*/;
  }
};

这有几个缺点:


  • 验证码是多余的。 (在构造函数和设置器中)

  • 规则通常适用于该类,而不仅适用于特定方法。应该在类中指定它们,而不是在特定方法中指定它们。

是否可以使用C ++做得更好17/14/17元编程?

理想情况下,结果类似于以下内容:

Ideally, the outcome would be similar to this:

class MyClass {
  double x;  // Must be in [0;500].

  /* Write all validation rules in a central place: */
  REGISTER_CONDITION(x, (x >= 0.0));
  REGISTER_CONDITION(x, (x <= 500.0));

public:

  MyClass(double _x) : x(_x) {
    validate(x);  // Tests all conditions that have been registered for x.
  }

  void SetX(double _x) {
    x = _x;
    validate(x);  // Tests all conditions that have been registered for x.
  }
};






注意:
此验证功能将包含在名为 合同。但是,它并没有进入C ++ 17标准[需要引用]。


Note: This validation functionality would be covered by a proposed addition to the C++ standard named "contracts". However, it has not made it into the C++17 Standard [citation needed].

推荐答案

经过几天的思考,我可以提供基于C ++ 11模板的对象验证机制:

After a few days of thinking, I can provide an object validation mechanism based on C++11 templates:

class MyClass {
  double x; // Must be in [0;500].
  double y; // Must be in [2x;3x].

  /* Register test expressions. */
  VALID_EXPR( test_1, x >= 0.0 );
  VALID_EXPR( test_2, x <= 500.0 );
  VALID_EXPR( test_3, y >= 2*x );
  VALID_EXPR( test_4, y <= 3*x );

  /* Register test expressions with involved data members. */
  VALIDATION_REGISTRY( MyClass,
    REGISTER_TEST( test_1, DATA_MEMBER(&MyClass::x) ),
    REGISTER_TEST( test_2, DATA_MEMBER(&MyClass::x) ),
    REGISTER_TEST( test_3, DATA_MEMBER(&MyClass::x), DATA_MEMBER(&MyClass::y) ),
    REGISTER_TEST( test_4, DATA_MEMBER(&MyClass::x), DATA_MEMBER(&MyClass::y) )
  );

public:

  MyClass(double _x, double _y) : x(_x), y(_y) {
    validate(*this);  // Tests all constraints, test_1 ... test_4.
  }

  void SetX(double _x) {
    x = _x;
    // Tests all constraints that have been registered for x,
    // which are test_1 ... test_4:
    validate<MyClass, DATA_MEMBER(&MyClass::x)>(*this);
  }

  void SetY(double _y) {
    y = _y;
    // Tests all constraints that have been registered for y,
    // which are test_3 and test_4:
    validate<MyClass, DATA_MEMBER(&MyClass::y)>(*this);
  }
};

此注册和实施的实现验证机制使用以下方法

The implementation behind this registration & validation mechanism uses the following Approach:


  • 将编译时信息存储为类型。

  • 在模板参数包中注册验证检查。

  • 提供帮助程序宏以简化庞大的C ++模板表示法。

此解决方案的优势:


  • 数据模型的约束在

  • 数据模型的约束被实现为数据模型的一部分,而不是操作的一部分。

  • 任意测试表达式是可能的,例如 x> 0&&晶圆厂(x)< pow(x,y)

  • 利用模型约束在编译时就知道了。

  • 用户拥有控制执行验证的时间。

  • 可以用单行代码调用验证。

  • 编译器优化应将所有检查折叠为简单参数检查。与许多 if-then 构造相比,应该没有额外的运行时开销。

  • 因为可以将测试表达式链接到

  • Constraints of the data model are listed in a single, central location.
  • Constraints of the data model are implemented as part of the data model, not as part of an operation.
  • Arbitrary test expressions are possible, e.g. x > 0 && fabs(x) < pow(x,y).
  • Exploits that model constraints are known at compile-time.
  • The user has control over when validation is performed.
  • Validation can be invoked with a single line of code.
  • The compiler optimization should collapse all checks into simple parameter checks. There should be no additional run-time overhead compared to a number of if-then constructs.
  • Because test expressions can be linked to involved data members, only the relevant tests will be performed.

此解决方案的缺点:


  • 验证失败时,对象将保持无效状态。用户将必须实现自己的恢复机制。

可能的扩展名:


  • 特殊类型的异常(例如 Validation_failure )。

  • 为多个数据成员调用 validate

  • Special type of exception to throw (e.g. Validation_failure).
  • Calling validate for more than one data member.

这只是一个我的想法。我确信很多方面仍然可以改进。

This is just an idea of mine. I am sure that many aspects can still be improved.

以下是示例的驱动代码,可以将其放在在头文件中:

Here is the driving code for the example, which could be placed in a header file:

template<class T>
struct remove_member_pointer { typedef T type; };

template<class Parent, class T>
struct remove_member_pointer<T Parent::*> { typedef T type; };

template<class T>
struct baseof_member_pointer { typedef T type; };

template<class Parent, class T>
struct baseof_member_pointer { typedef Parent type; };

template<class Class>
using TestExpr = void (Class::*)() const;

template<class Type, class Class, Type Class::*DP>
struct DataMemberPtr {
  typedef Type type;
  constexpr static auto ptr = DP;
};

#define DATA_MEMBER(member) \
  DataMemberPtr< \
    remove_member_pointer<decltype(member)>::type, \
    baseof_member_pointer<decltype(member)>::type, member>

template<class ... DataMemberPtrs>
struct DataMemberList { /* empty */ };

template<class Ptr, class ... List>
struct contains : std::true_type {};

template<class Ptr, class Head, class ... Rest>
struct contains<Ptr, Head, Rest...>
  : std::conditional<Ptr::ptr == Head::ptr, std::true_type, contains<Ptr,Rest...> >::type {};

template<class Ptr>
struct contains<Ptr> : std::false_type {};

template<class Ptr, class ... List>
constexpr bool Contains(Ptr &&, DataMemberList<List...> &&) {
  return contains<Ptr,List...>();
}

template<class Class, TestExpr<Class> Expr, class InvolvedMembers>
struct Test {
  constexpr static auto expression = Expr;
  typedef InvolvedMembers involved_members;
};

template<class ... Tests>
struct TestList { /* empty */ };

template<class Class, int X=0>
inline void _RunTest(Class const &) {} // Termination version.

template<class Class, class Test, class ... Rest>
inline void _RunTest(Class const & obj)
{
  (obj.*Test::Expression)();
  _RunTest<Class, Test...>(obj);
}

template<class Class, class Member, int X=0>
inline void _RunMemberTest(Class const &) {} // Termination version.

template<class Class, class Member, class Test, class ... Rest>
inline void _RunMemberTest(Class const & obj)
{
  if (Contains(Member(), typename Test::involved_members()))
    (obj.*Test::Expression)();
  _RunMemberTest<Class,Member,Rest...>(obj);
}

template<class Class, class ... Test>
inline void _validate(Class const & obj, TestList<Tests...> &&)
{
 _RunTest<Class,Tests...>(obj);
}

template<class Class, class Member, class ... Tests>
inline void validate(Class const & obj, Member &&, TestList<Tests...> &&)
{
  _RunMemberTest<Class, Member, Tests...>(obj);
}

#define VALID_EXPR(name, expr) \
  void _val_ ## Name () const { if (!(expr)) throw std::logic_error(#expr); }

#define REGISTER_TEST(testexpr, ...) \
  Test<_val_self, &_val_self::_val_ ##testexpr, \
    DataMemberList<__VA_ARGS__>>

#define VALIDATION_REGISTRY(Class, ...) \
  typedef Class _val_self; \
  template<class Class> \
  friend void ::validate(Class const & obj); \
  template<class Class, class DataMemberPtr> \
  friend void ::validate(Class const & obj); \
  using _val_test_registry = TestList<__VA_ARGS__>

/* Tests all constraints of the class. */
template<class Class>
inline void validate(Class const & obj)
{
  _validate(obj, typename Class::_val_test_registry() );
}

/* Tests only the constraints involving a particular member. */
template<class Class, class DataMemberPtr>
inline void validate(Class const & obj)
{
  _validate(obj, DataMemberPtr(), typename Class::_val_test_registry() );
}

(注意:在生产环境中,大多数会将这些内容放入单独的命名空间。)

(Note: In a production environment, one would put most of this into a separate namespace.)

这篇关于C ++ 11中的对象验证逻辑模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆