使用变分模板的困难

Difficulties using Variadic Templates

本文关键字:      更新时间:2023-10-16

我正在编写一个与网络相关的类。我的应用程序接收形式为的网络消息

[uint8_t message id, uint8_t/uint16_t/uint32_t data ...]

我的类允许其用户为特定的消息id注册回调。

由于有各种不同的消息,具有不同数量的不同数据条目(数据条目仅限于uint8_t、uint16_t和uint32_t),我决定使用C++11的可变模板来减轻重复代码的负担。

这是我想做的伪代码(没有编译,怀疑它是否编译)

#include <arpa/inet.h>
#include <stdexcept>
using namespace std;
template<class ...T>
struct MessageHandler {
    size_t size;
    std::function<void(T...)> callback;
    template<class Head, class... Tail>
    void parseHelper(uint8_t *data)
    {
        if (sizeof(Head) == 1) {
            uint8_t val;
            memcpy(&val, data, sizeof(Head));
            // set next unset argument to the value of val
            callback = std::bind(callback, val);
            data += sizeof(Head);
        } else if (sizeof(Head) == 2) {
            uint16_t val;
            memcpy(&val, data, sizeof(Head));
            val = ntohs(val);
            // set next unset argument to the value of val
            callback = std::bind(callback, val);
            data += sizeof(Head);
        } else if (sizeof(Head) == 4) {
            uint32_t val;
            memcpy(&val, data, sizeof(Head));
            val = ntohl(val);
            // set next unset argument to the value of val
            callback = std::bind(callback, val);
            data += sizeof(Head);
        } else {
            throw std::invalid_argument("We support only 1, 2 and 4 byte integers!");
        }
        // repeat for the rest of arguments
        parseHelper<Tail...>(data);
    }
    template<class ...Empty>
    void parseHelper(uint8_t *data)
    {
        // do nothing, terminating case of recursion
    }
    template<class ...T>
    void parse(utin8_t *data)
    {
        // parse `data` into T... arguments and bind them into `callback`
        parseHelper<T...>(data);
        // at this point `callback` has all arguments binded from `data`
        // invoke the callback
        callback();
    }
}
// <message id, callback-holding helper struct>
std::unordered_map<uint8_t, MessageHandler> myMap;
template<class...T>
void dummy(T&&...)
{
    // a dummy, does nothing
}
template<class...T>
void addMessageHandler(uint8_t messageId, std::function<void<T... arg>> callback)
{
    MessageHandler<arg> mh;
    mh.size = 0;
    // order of execution is undefined, but we don't care
    dummy( (mh.size += sizeof(arg))... );
    mh.callback = callback;
    myMap[messageId] = mh;
}
void foo(uint16_t a, uint8_t b, uint16_t c, uint32_t d)
{
    // do stuff with the parsed message
}
void bar(uint32_t a)
{
    // do stuff with the parsed message
}
int main()
{
    // register callbacks
    addMessageHandler<uint16_t, uint8_t, uint16_t, uint32_t>(0, std::bind(&foo));
    addMessageHandler<uint32_t>(1, std::bind(&bar));
    ...
    // get message over the network
    uint8_t messageId = some_network_library.read.first_byte();
    MessageHandler mh = myMap[messageId];
    uint8_t *data = some_network_library.read.bytes(mh.size);
    // parses and calls the callback with parsed values
    mh.parse(data);
    return 0;
}

主要来说,我们为消息ID注册回调,然后通过网络接收消息,获取适当的MessageHandler,逐个变量解析data,将每个回调附加到回调绑定中,当我们绑定了所有内容时,调用回调。

所以,我关心的事情:

  1. 是否可以有一个映射(或其他基于整数键结构值的数据结构,具有近似常量查找),其中的值是一个模板结构,并且您希望在其中存储不同类型的结构?(即存储在映射中的值不是同质类型的)。

  2. 我需要什么才能使parseparseHelper函数工作?

    • 我不确定是否可以像那样将绑定值附加到std::函数中
    • 调用parse中的回调后,如何解除所有绑定值的绑定?(或者他们在通话后自动解除绑定?)

如何使此代码正常工作?

如果有人能把我的伪代码修复成一个有效的代码,解释为什么我的代码不起作用以及它是如何修复的,那就太好了,但只是解释也非常有帮助!

  1. 参数多态性(模板)不是包含多态性(继承):MessageHandler<int>MessageHandler<float>是不同的类型,没有一个可用于另一个("基类")的通用定义。因此,不能创建一个可以存储具有不同参数的MessageHandler的容器

还要记住,静态类型还意味着要知道声明的大小。如果不将参数求解为其实际"值",这是不可能的。

所以没有。如果没有实际指定T,就不能有map<key, MessageHandler<T...>>,这禁止为T...使用多个值。

为了解决这个问题,你可以使用一个类型橡皮擦。我们使用这个例子:

https://github.com/aerys/minko/blob/master/framework/include/minko/Any.hpp

所以我们可以创建一个CCD_ 11。

  1. 如果你想有一个可变回调方法,你可以看看我们的Signal类:

https://github.com/aerys/minko/blob/master/framework/include/minko/Signal.hpp

它使用可变模板调用回调,并将相应的参数作为参数。

在您的parseHelper函数的情况下,我认为它有多个问题:

  • 它只需要"head"值,难道不需要某种循环/递归调用吗
  • 如果你做这样的循环,它什么时候应该停止?你需要指针和你正在阅读的"消息"的大小
  • 我认为您应该使用lambdas而不是std::bind:在您的情况下,一切都是单态的,所以std::绑定将免费占用更多的内存/CPU
  • 您不想调用callback而不是设置它吗?我以为callback是用户定义的值

我认为您想要做的是"反序列化"来自网络的值集,然后将这些值作为回调的参数传递。这是正确的吗?

如果是这样的话,你可以看看这个:https://stackoverflow.com/a/1547118/4525791

您可以通过模板参数轻松地解析内存中的动态数据(请参阅第1部分)。关于如何使用元组调用函数,答案非常有用,并且可以应用(请参阅第2部分)。现在,您只需要存储有关函数的信息,并使用动态解析的值进行调用。此外,从我的角度来看,在阅读时查找消息的大小可能会有另一个问题,所以我为它制作了简单的助手struct

template< typename T1, typename... T2 >
struct size_of {
    enum {
        size = sizeof (T1) + size_of < T2... >::size
    };
};
template< typename T >
struct size_of< T > {
    enum {
        size = sizeof (T)
    };
};

这是带有一些注释的代码

  1. 分析程序

    template< typename T1, typename... T2 >
    struct parser {
        static std::tuple< T1, T2... > parse(void* data) {
            // get value from pointer
            T1* p = (T1*) data;
            std::cout << typeid (*p).name() << " " << *p << std::endl;
            // concatenate current value with next one 
            return std::tuple_cat(std::make_tuple(*p),
                    parser < T2... >::parse(p + 1));
        }
    };
    template< typename T1 >
    struct parser< T1 > {
        static std::tuple< T1 > parse(void* data) {
            T1* p = (T1*) data;
            std::cout << typeid (*p).name() << " " << *p << std::endl;
            return std::make_tuple(*p);
        }
    };
    

    此外,您还可以重新实现这个类,以返回解析值的大小,以确保一切正常

  2. 函数调用

    // function call using tuple 
    template < int N >
    struct __apply_impl {
        template < typename... ArgsF, typename... ArgsT, typename... Args >
        static void apply(const std::function<void( ArgsF...)>& f,
                const std::tuple<ArgsT...>& t,
                Args... args) {
            __apply_impl < N - 1 > ::apply(f, t, std::get < N - 1 > (t), args...);
        }
    };
    template <>
    struct __apply_impl<0> {
        template < typename... ArgsF, typename... ArgsT, typename... Args >
        static void apply(const std::function<void( ArgsF...)>& f,
                const std::tuple<ArgsT...>& /* t */,
                Args... args) {
            // actual call
            f(args...);
        }
    };
    // wrapper function
    template < typename... ArgsF, typename... ArgsT >
    void call_with_tuple(const std::function<void( ArgsF...)>& f,
            std::tuple<ArgsT...> const& t) {
        __apply_impl<sizeof...(ArgsT)>::apply(f, t);
    }
    
  3. 消息分配器或消息处理器

    // message dispatcher
    class message_dispatcher {
    protected:
        // callback interface
        struct callback_t {
        public:
            virtual void call(void*) = 0;
        };
        // and implementation
        template< typename... Ty >
        struct callback_impl : public callback_t {
            typedef std::function< void(Ty...) > function_t;
            callback_impl(const function_t& f) {
                m_f = f;
            }
            virtual void call(void* data) {
                // parse to tuple
                auto t = parser < Ty... >::parse(data);
                // call function
                call_with_tuple(m_f, t);
            }
            function_t m_f;
        };
    public:
        // process incoming data 
        void process(int t, void* data) {
            m_c[t]->call(data);
        }
        // register callback for type t
        template< typename... Ty >
        void add(int t, const std::function< void(Ty...) >& f) {
            m_c[t] = new callback_impl < Ty... >(f);
        }
    protected:
        std::map< int, callback_t* > m_c;
    };
    
  4. 示例

    void foo(int a, float b, char c) {
        std::cout << "in foo(int,float,char) with args: ";
        std::cout << "1: " << a << ", "
                << "2: " << b << ", "
                << "3: " << c << std::endl;
    }
    struct foo_t {
        void foo(int a, double b) {
            std::cout << "in foo_t::foo(int,double) with args: ";
            std::cout << "1: " << a << ", "
                    << "2: " << b << std::endl;
        }
    };
    int main(int argc, char** argv) {
        // pack data 
        char* b1 = new char[size_of< int, float, char >::size];
        int a1 = 1;
        float a2 = 2.;
        char a3 = 'a';
        memcpy(b1, &a1, sizeof (a1));
        memcpy(b1 + sizeof (a1), &a2, sizeof (a2));
        memcpy(b1 + sizeof (a1) + sizeof (a2), &a3, sizeof (a3));
        // pack data 
        char* b2 = new char[size_of< int, double >::size];
        int a4 = 10;
        double a5 = 20.;
        memcpy(b2, &a4, sizeof (a4));
        memcpy(b2 + sizeof (a4), &a5, sizeof (a5));
        // create callbacks
        std::function<void(int, float, char) > f1(&foo);
        foo_t foo;
        std::function<void(int, double) > f2 = std::bind(&foo_t::foo, &foo,
                std::placeholders::_1,
                std::placeholders::_2);
        message_dispatcher md;
        // register callbacks
        md.add(0, f1);
        md.add(1, f2);
        // call 
        md.process(0, b1);
        md.process(1, b2);
        return 0;
    }
    
  5. 输出

    i 1
    f 2
    c a
    in foo(int,float,char) with args: 1: 1, 2: 2, 3: a
    i 10
    d 20
    in foo_t::foo(int,double) with args: 1: 10, 2: 20
    

当然,它只适用于POD类型。我没有使用uint8_tuint16_tuint32_t,但它们不会有问题。

相关文章:
  • 没有找到相关文章