首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用于网络头解析的静态多态的数组视图适配器类

用于网络头解析的静态多态的数组视图适配器类
EN

Code Review用户
提问于 2016-08-31 15:57:48
回答 1查看 202关注 0票数 7

一些上下文:我有一些现有的代码来填充来自istreams的网络头结构。令人振奋的节选:

代码语言:javascript
复制
struct l2_eth_frame {
    using mac_address_t = std::array<std::uint8_t, 6>;
    mac_address_t dest, src;
    std::uint16_t type;
    std::uint32_t vlan;
};

template<typename StreamLike, typename T>
void read_ntoh(StreamLike &is, T &t){
    static_assert(std::is_integral<T>::value, "fixed width integer required");
    is.read(reinterpret_cast<char*>(&t), sizeof(T));

    uint8_t *p = reinterpret_cast<uint8_t*>(&t);
    std::reverse(p, p + sizeof(T));
}

template<class StreamLike>
l2_eth_frame read_l2_eth_frame(StreamLike &i)
{
    l2_eth_frame e;
    i.read(reinterpret_cast<char*>(&e.dest), sizeof(e.dest));
    i.read(reinterpret_cast<char*>(&e.src), sizeof(e.src));
    read_ntoh(i, e.type);
    if(e.type == 0x8100){
        i.read(reinterpret_cast<char*>(&e.vlan), sizeof(e.vlan));
    }
    return e;
}

int main() {
     std::ifstream ifs("test.pcap");
     auto eth = read_l2_eth_frame(ifs);
}

我现在正在尝试重用这些代码,以便能够从实时数据(例如从原始套接字)生成网络结构。为了实现这一点,我编写了一个简单的array_view类和一个适配器类,它为它提供了readignore方法(如istream),以便它能够在read_{header_type}方法中使用静态多态性。

array_view

代码语言:javascript
复制
#pragma once

#include <cassert>
#include <memory>
#include <array>

template <class T>
class basic_array_view {

private:
    const T* array;
    std::size_t len;

public:
    static constexpr std::size_t npos = -1;

    basic_array_view() noexcept :
        array(nullptr),
        len(0)
    {}

    basic_array_view(const T* array, std::size_t len) noexcept :
        array(array),
        len(len)
    {}

    template<std::size_t N>
    basic_array_view(const T (& a)[N]) noexcept :
        array(std::addressof(a[0])),
        len(N)
    {}

    template<std::size_t N>
    basic_array_view(const std::array<T, N> &a) noexcept :
        array(a.data()),
        len(N)
    {}

    basic_array_view(const basic_array_view &other) = default;
    basic_array_view& operator=(const basic_array_view &other) = default;

    constexpr std::size_t size() const noexcept {
        return len;
    }

    const T& operator[](std::size_t pos) const noexcept {
        assert(pos < len);
        return *(array + pos);
    }

    const T* data() const noexcept {
        assert(len > 0);
        return array;
    }

    basic_array_view<T> subview_right(std::size_t pos=0, std::size_t count=npos){
        assert(pos <= len);
        return { array + pos, std::min(count, len - pos) };
    }
};

using array_view = basic_array_view<char>;

StreamLikeArrayView

代码语言:javascript
复制
#include "array_view.h"

class StreamLikeArrayView {

public:
    array_view av;

    template<class... Args>
    StreamLikeArrayView(Args&&... args) :
        av(std::forward<Args>(args)...) {}

    void read(char *dest, std::size_t len){
        std::copy(av.data(), av.data() + len, dest);
        av = av.subview_right(len, array_view::npos);
    }

    void ignore(std::size_t len){
        av = av.subview_right(len, array_view::npos);
    }
};

使用

代码语言:javascript
复制
std::array<char, 4096> buf;
const auto bytes_read = ::recv(sd, buf.data(), buf.size(), 0); //assume we have set up some raw socket sd
StreamLikeArrayView slav{buf.data(), bytes_read};
const auto eth = read_l2_eth_frame(slav);

我特别感兴趣的是对StreamLikeArrayView抽象的回顾,以及是否有什么更好的方法可以在istreams和数组之间重用解析代码。

EN

回答 1

Code Review用户

发布于 2016-09-11 20:59:04

可以是更通用的

让我们考虑一下您的一个构造函数:

代码语言:javascript
复制
template<std::size_t N>
basic_array_view(const std::array<T, N> &a) noexcept :
    array(a.data()),
    len(N)
{}

我不认为有什么特别好的理由应该/必须限制在std::array中使用。我想我会写更像这样的代码:

代码语言:javascript
复制
template<class Container>
basic_array_view(Container const &a) noexcept :
    array(a.data()),
    len(a.length())
{}

现在它可以与std::array或其他容器(如std::vectorstd::deque等)一起使用。

不完全

array_view这样的东西的基本点是(至少通常)提供与现有集合相同的接口。它确实提供了很大一部分,但它缺少了许多东西,比如value_typereference_typeiterator_type等的普通类型。它还缺少对内容的迭代器访问;考虑到您处理的是一个连续数组,可以提供非常小的内容(begin()end()分别返回指针到开始和结束后的一个指针)。后者将允许与标准算法等一起使用。

命名

我会试着选择一个命名惯例并坚持下去。无论是snake_case with还是PascalCase全程--但将array_viewStreamLikeArrayView结合起来有点不太好。

我还认为subview_right是一个糟糕的名字--似乎subview是完全足够的。

复制

在某些方面,这是最大的问题。您可以通过简单地将数据转储到一个字符串流中,并让您的现有代码从中读取,从而获得StreamLikeArrayView的功能。(初选?)好处是避免复制数据,就像使用正常的字符串一样。

如果您的真正目的是提供类似于字符串流的功能,但通过让它使用一个非streambuf拥有的缓冲区来避免复制,那么我认为更直接地提供这样的功能会更好:

代码语言:javascript
复制
struct array_buffer : public std::streambuf {
    array_buffer(char const *begin, char const *end) {
        char *b = const_cast<char *>(begin);
        char *e = const_cast<char *>(end);

        setg(b, b, e);
    }

    template <class Container>
    array_buffer(Container const &c) : array_buffer(&*std::begin(c), &*std::end(c))
    { }
};

我要补充的是:这也有点不完整--它并不试图支持所有可能的流缓冲区操作,但它至少支持上面的类。

为此,我们可以提供一个流类,以节省用户在构建基于缓冲区类型的流时所做的工作:

代码语言:javascript
复制
class buffer_stream : public std::istream {
    array_buffer buf;
public:
    template<class... Args>
    buffer_stream(Args&&... args) : buf(std::forward<Args>(args)...), std::istream(&buf)
    { }
};

接下来是一些演示代码,演示如何从多个源中的任何一个构建这种类型的istream,以及从istream读取信息的常用方法如何与此istream一起工作:

代码语言:javascript
复制
int main() {
    char input[] = "This is some input";

    // Create a buffer stream reading from an array
    buffer_stream b(input);

    std::string s;

    // Do some normal stream reading from it:
    while (b >> s)
        std::cout << s << "\n";

    // Create another from an `std::array`, read some from it:
    // Sorry, but I'm too lazy to hand-craft a very interesting array here:
    std::array<char, 3> more_input { 65, 66, 67 };

    buffer_stream c(more_input);

    while (c >> s)
        std::cout << s << "\n";

    // Create one from a string and read from it:
    std::string still_more_input { "This is the last piece of input" };

    buffer_stream d(still_more_input);

    // Read the first few words:
    char buffer[11];
    d.read(buffer, sizeof(buffer));
    std::cout.write(buffer, sizeof(buffer));
    std::cout << "\n";

    // Skip " last "
    d.ignore(6);

    // Then read the remainder into a string:
    std::getline(d, s);
    std::cout << s << "\n";
}

提供从istream派生的类的明显优点是,它基本上可以处理从istream读取的所有内容,而不仅仅是使用一些自己选择的函数来模板类似流的对象,并将自己限制在两种特定的从流读取的方式上。

这种方法的明显缺点是,这可能会增加更多的开销,有些人(尤其是那些没有钻研过流、缓冲区等的人)。可能会发现很难理解。还有一个事实是,通过成为流缓冲区类,人们可能会认为这支持任何流缓冲区可能提供的一切,这比它实际提供的要多一点。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/140138

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档