一些上下文:我有一些现有的代码来填充来自istreams的网络头结构。令人振奋的节选:
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类和一个适配器类,它为它提供了read和ignore方法(如istream),以便它能够在read_{header_type}方法中使用静态多态性。
#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>;#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);
}
};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和数组之间重用解析代码。
发布于 2016-09-11 20:59:04
让我们考虑一下您的一个构造函数:
template<std::size_t N>
basic_array_view(const std::array<T, N> &a) noexcept :
array(a.data()),
len(N)
{}我不认为有什么特别好的理由应该/必须限制在std::array中使用。我想我会写更像这样的代码:
template<class Container>
basic_array_view(Container const &a) noexcept :
array(a.data()),
len(a.length())
{}现在它可以与std::array或其他容器(如std::vector、std::deque等)一起使用。
像array_view这样的东西的基本点是(至少通常)提供与现有集合相同的接口。它确实提供了很大一部分,但它缺少了许多东西,比如value_type、reference_type、iterator_type等的普通类型。它还缺少对内容的迭代器访问;考虑到您处理的是一个连续数组,可以提供非常小的内容(begin()和end()分别返回指针到开始和结束后的一个指针)。后者将允许与标准算法等一起使用。
我会试着选择一个命名惯例并坚持下去。无论是snake_case with还是PascalCase全程--但将array_view与StreamLikeArrayView结合起来有点不太好。
我还认为subview_right是一个糟糕的名字--似乎subview是完全足够的。
在某些方面,这是最大的问题。您可以通过简单地将数据转储到一个字符串流中,并让您的现有代码从中读取,从而获得StreamLikeArrayView的功能。(初选?)好处是避免复制数据,就像使用正常的字符串一样。
如果您的真正目的是提供类似于字符串流的功能,但通过让它使用一个非streambuf拥有的缓冲区来避免复制,那么我认为更直接地提供这样的功能会更好:
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))
{ }
};我要补充的是:这也有点不完整--它并不试图支持所有可能的流缓冲区操作,但它至少支持上面的类。
为此,我们可以提供一个流类,以节省用户在构建基于缓冲区类型的流时所做的工作:
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一起工作:
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读取的所有内容,而不仅仅是使用一些自己选择的函数来模板类似流的对象,并将自己限制在两种特定的从流读取的方式上。
这种方法的明显缺点是,这可能会增加更多的开销,有些人(尤其是那些没有钻研过流、缓冲区等的人)。可能会发现很难理解。还有一个事实是,通过成为流缓冲区类,人们可能会认为这支持任何流缓冲区可能提供的一切,这比它实际提供的要多一点。
https://codereview.stackexchange.com/questions/140138
复制相似问题