前言 本工程使用CRTP实现编译阶段控制权限的Context数据访问框架。具体目标为:
给不同的节点 Node 提供访问 DictContext 的能力。同时用模板 + static_assert 保证: Node 只能读 IN_KEYS,写 OUT_KEYS。否则编译报错。
PermissionKey——Key的类型系统 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #ifndef PERMISSIONS_KEY_H #define PERMISSIONS_KEY_H #include <string> template <typename T_tag, typename T>struct PermissionKey { typedef T type; const char * name; }#define DEFINE_KEY(key_name, type) \ struct key_name##_tag{}; \ static constexpr PermissionKey<key_name##_tag,type> (key_name){#key_name}; \ using key_name##_type = decltype(key_name); DEFINE_KEY (NAME, std::string);DEFINE_KEY (OLD, int );DEFINE_KEY (TEL, std::string);DEFINE_KEY (WORKID, std::string);DEFINE_KEY (HEIGHT, float );#endif
具体解释如下:
每个Key都是独立的类型(由tag唯一标识)
每个Key自带一个value类型(type),用于限定其对应的值类型
每个Key有一个字符串名字name,用于做运行时字典查找
以DEFINE_KEY(NAME, std::string)为例,其将会被展开为如下形式。即定义了一个独立的类型NAME_tag,并生成一个模板实例PermissionKey<NAME_tag, std::string> NAME{"NAME"},NAME内部维护了它的类型type为std::string,名字为NAME。最后使用NAME_type来表示PermissionKey的一个实例,以用于后续的模板匹配。
1 2 3 struct NAME_tag {};static constexpr PermissionKey<NAME_tag, std::string> NAME{"NAME" };using NAME_type = PermissionKey<NAME_tag, std::string>;
DictContext——字典容器,运行时存储Key-Value DictContext是一个字典容器,其内部维护一个unorder_map_,用于存储键值对。通过key.name从字典中找到对应的值,然后通过std::any_cast将值转换为限定的类型K::type。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <unorder_map> #include <string> #include <any> #include "permission_key.hpp" #include <memory> class DictContext {public : typedef std::shard_ptr<DictContext> Ptr; template <typename K> typename K::type Get (K key) { auto iter = unordered_map_.find (key.name); if (iter != unordered_map_.end ()) { return std::any_cast <typename K::type>(unordered_map_[key.name]); } else { throw std::runtime_error ("Dict Context don't find the key" ); } } template <typename K> void Set (K key, typename K::type value) { unordered_map_[key.name] = value; } private : std::unorder_map<std::string, std::any> unorder_map_; }
ContainKey/ContainAnyKey——编译期检查Key是否属于某个tuple列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <tuple> template <typename T, typename Tuple> struct ContainKey ;template <typename T> struct ContainKey <T, std::tuple<>>:std::false_type{};template <typename T, typename Tfirst, typename ... Trst>struct ContainKey <T, std::tuple<Tfirst, Trst...>>: std::conditional<std::is_same< typename std::remove_const<T>::type, typename std::remove_const<Tfirst>::type>::value, std::true_type, ContainKey<T, std::tuple<Tfirst...>>>::type{}; template <typename T, typename ... Tuples>struct ContainAnyKey ;template <typename T>struct ContainAnyKey <T>: std::false_type {};template <typename T, typename First, typename ... Rest>struct ContainAnyKey <T, First, Rest...>:std::conditional< ContainKey<T, First>::value, std::true_type, ContainAnyKey<T, Rest...> >::type {};
ContextHandle——提供get/set,并用static_assert做权限检查 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include "dict_context.hpp" #include "contain_key_type_match.hpp" class ContextHandle {public : explicit ContextHandle (DictContext::Ptr dictContext) : dictContextPtr_(std::move(dictContext)){ }; ~ContextHandle (){}; }public : ContextHandle ()=delete ; ContextHandle (const ContextHandle&)=delete ; ContextHandle& operator =(const ContextHandle&)=delete ; ContextHandle (const ContextHandle&&)=delete ; ContextHandle& operator =(const ContextHandle &&)=delete ; public : template <typename K, typename KFirst> typename K::type get (K key) { static_assert (ContainAnyKey<K, KFirst>::value, "The key is not be allowed to get!" ) return dictContextPtr_->Get (key); } template <typename K, typename KFirst, typename K2, typename ...Kest> typename K::type get (K key) { static_assert (ContainAnyKey<K, KFirst, K2, Kest...>::value, "The key is not be allowed to get!" ) return dictContextPtr_->Get (key); } template <typename K, typename KFirst> void set (K key, typename K::type value) { static_assert (ContainAnyKey<K, KFirst>::value, "The key is not be allowed to set!" ) dictContextPtr_->Set (key, value); } template <typename K, typename KFirst, typename K2, typename ...Kest> void set (K key, typename K::type value) { static_assert (ContainAnyKey<K, KFirst, K2, Kest...>::value, "The key is not be allowed to set!" ) dictContextPtr_->Set (key, value); } private : DictContext::Ptr dictContextPtr_;
BaseNode——为每个Node声明可读Key和可写Key 使用CRTP实现颠倒继承,父类BaseNode可以拿到子类Derived中的IN_KEYS和OUT_KEYS,在此基础上通过ContextHandle为子类添加权限检查功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include "dict_context.hpp" #include "permission_key.hpp" #include "contain_key_type_match.hpp" #include "context_data_handle.hpp" tempalte<typename Derived> Class BaseNode {public : BaseNode (DictContext::Ptr dictContextPtr): contextHandle_ (dictContextPtr){} public : template <typename K> typename K::type get (K key) { using IN_KEYS = typename Derived::IN_KEYS; using OUT_KEYS = typename Derived::OUT_KEYS; return contextHandle_.get <K, IN_KEYS, OUT_KEYS>(key); } public : template <typename K> void set (K key, typename K::type value) { USING OUT_KEYS = typename Derived::OUT_KEYS; contextHandle_.set <K, OUT_KEYS>(key, value); } public : ContextHandle contextHandle_; }
Node —— 指明本节点的权限 Node是BaseNode<Node>的子类,其通过IN_KEYS和OUT_KEUYS决定可读Key和可写Key`。
以node.set(NAME, "xxx")为例,我们来解释一下代码执行的流程:
匹配到Node::set,因为Node没有自定义set调用BaseNode::set。此时:
K = NAME_type
key = NAME
value = std::string("xxx")
OUT_KEYS = std::tuple<NAME_type, OLD_type, HEIGHT_type>
进入BaseNode::set→调用ContextHandle::set 此时匹配到ContextHandle的模板函数:
1 2 template <typename K, typename KFirst, typename K2, typename ...Krest>void set (K key, typename K::type value)
其中:
K = NAME_type
KFirst = OUT_KEYS = std::tuple<NAME_type, OLD_type, HEIGHT_type>
没有 K2, Krest 参数 因此,匹配到单参数版本的set:
1 2 3 4 5 6 template <typename K, typename KFirst>void set (K key, typename K::type value) { static_assert (ContainAnyKey<K, KFirst>::value, "The key is not be allowed to set!" ); dictContextPtr_->Set (key, value); }
static_assert权限检查(编译期发生)ContainAnyKey展开为ContainKey:
1 ContainKey<NAME_type, std::tuple<NAME_type, OLD_type, HEIGHT_type>>
此处为true,编译通过。 如果NAME不在OUT_KEYS中,编译期报错。
ContextHandle调用具体逻辑
1 dictContextPtr_->Set (key, value);
进入DictContext::Set
1 2 3 4 template <typename K>void Set (K key, typename K::type value) { unordered_map_[key.name] = value; }
其中:
key.name = "NAME"
value = std::string("xxx")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include "context_data_handle.hpp" #include <iostream> #include "base_node_type.hpp" class Node : public BaseNode<Node> {public : Node (): BaseNode <Node>(std::make_shared <DictContext>()){} using IN_KEYS = std::tuple<WORKID_type, OLD_type, HEIGHT_type>; using OUT_KEYS = std::tuple<NAME_type, OLD_type, HEIGHT_type>; };int main (int argc, char ** argv) { Node node{}; node.set (NAME, "xxx" ); try { auto name = node.get (WORKID); std::cout << "my name: " << name << std::endl; } catch (std::exception e) { std::cerr << e.what () << std::endl; } node.set (HEIGHT, 1.50 ); auto height = node.get (HEIGHT); std::cout << "my height: " << height << std::endl; return 0 ; }