我想让我的各种模板类进行检查。
首先解释一下它应该做什么:
我正在编写一个应用程序,在其中读取块数据。
在我阅读数据之前,我知道哪些格式是可以期待的。
例如:
ID123.EnableValve := FALSE;
ID123.Position1 := 0;
ID123.Position2 := 2;
ID123.Position3 := 9; 或者:
UDT30.EnableValve := FALSE;
UDT30.This := 0;
UDT30.is := L#2;
UDT30.an := 9;
UDT30.Example := TRUE; 这些数据块有以下相似之处:
UDT30或ID123)。EnableValve或Position3)。Position1不会发生两次)。:=总是跟随。TRUE、0、L#)。4或5)。我想对数据库锁执行以下操作:
std::istream)ID123.EnableValve := FALSE;变为ID123.EnableValve := TRUE;std::ostream)此外,还必须能够:
std::osstream)我希望继承此模板,并限制对行的set和get的使用。继承的类可以使用受保护的集/获取来决定要修改哪些行,以及哪些行是只读的。
我使用Google和Visual2017创建了模板的单元测试。在这里,您可以看到模板可以做什么。
#include "pch.h"
#include "..\variadic_templates\Fake_types.h"
// hack to test protected methods in variadic datablock
// is there a better way??
#define protected public
#include "..\variadic_templates\Variadic_datablock.h"
#include <sstream>
TEST(Variadic_templatesTest, DefaultConstructor) {
Variadic_datablock t;
EXPECT_EQ(t.get_id(), std::string{});
}
TEST(Variadic_templatesTest, Constructor) {
std::string id{ "ID123" };
EnableValve enableValve{ "TRUE" };
Position1 position1{ "0" };
Position2 position2{ "2" };
Position3 position3{ "3" };
Variadic_datablock<EnableValve, Position1, Position2, Position3> t{
id,
enableValve,
position1,
position2,
position3
};
EXPECT_EQ(t.get_id(), id);
EXPECT_EQ(t.get_element<EnableValve>().m, enableValve.m);
EXPECT_EQ(t.get_element<Position1>().m, position1.m);
EXPECT_EQ(t.get_element<Position2>().m, position2.m);
EXPECT_EQ(t.get_element<Position3>().m, position3.m);
}
TEST(Variadic_templatesTest, SetElement) {
std::string id{ "ID123" };
EnableValve enableValve{ "TRUE" };
Variadic_datablock<EnableValve> t{
id,
enableValve,
};
EXPECT_EQ(t.get_element<EnableValve>().m, enableValve.m);
enableValve.m = "FALSE";
t.set_element(enableValve);
EXPECT_EQ(t.get_element<EnableValve>().m, enableValve.m);
}
TEST(Variadic_templatesTest, TestIOstream) {
std::string input = {
R"( ID123.EnableValve := FALSE;
ID123.Position1 := 0;
ID123.Position2 := 2;
ID123.Position3 := 9;
)"
};
std::istringstream ist{ input };
Variadic_datablock<EnableValve, Position1, Position2, Position3> t;
ist >> t;
EXPECT_EQ(t.get_id(), "ID123");
EXPECT_EQ(t.get_element<EnableValve>().m, "FALSE");
EXPECT_EQ(t.get_element<Position1>().m, "0");
EXPECT_EQ(t.get_element<Position2>().m, "2");
EXPECT_EQ(t.get_element<Position3>().m, "9");
std::ostringstream ost;
ost << t;
EXPECT_EQ(ost.str(), input);
}#pragma once
#include "Read_from_line.h"
#include <tuple>
#include <iostream>
#include <string>
#include <vector>
#include <typeinfo>
template<typename ...T>
class Variadic_datablock
/*
Requires elements in T... are unique (not repeat of types)
*/
{
public:
Variadic_datablock() = default;
explicit Variadic_datablock(std::string id, T... args)
:m_id{ std::move(id) },
m_data{ std::move((std::tuple<T...>(args...))) }
{
}
std::string get_id() const
{
return m_id;
}
protected:
template<typename Type>
Type get_element() const
{
return std::get<Type>(m_data);
}
template<typename Type>
void set_element(Type a)
{
std::get<Type>(m_data) = a;
}
std::tuple<T...> get_data() const
{
return m_data;
}
private:
std::string m_id{};
std::tuple<T...> m_data{};
template<typename ...T>
friend std::ostream& operator<<(std::ostream& os,
const Variadic_datablock<T...>& obj);
template<typename ...T>
friend std::istream& operator>>(std::istream& is,
Variadic_datablock<T...>& obj);
};
template<class Tuple, std::size_t n>
struct Printer {
static std::ostream& print(
std::ostream& os, const Tuple& t, const std::string& id)
{
Printer<Tuple, n - 1>::print(os, t, id);
auto type_name =
extract_type_name(typeid(std::get<n - 1>(t)).name());
os << " " << id << "." << type_name << " := "
<< std::get<n - 1>(t) << "; " << '\n';
return os;
}
};
template<class Tuple>
struct Printer<Tuple, 1> {
static std::ostream& print(
std::ostream& os, const Tuple& t, const std::string& id)
{
auto type_name =
extract_type_name(typeid(std::get<0>(t)).name());
os << " " << id << "." << type_name << " := "
<< std::get<0>(t) << "; " << '\n';
return os;
}
};
template<class... Args>
std::ostream& print(
std::ostream& os, const std::tuple<Args...>& t, const std::string& id)
{
Printer<decltype(t), sizeof...(Args)>::print(os, t, id);
return os;
}
template<typename ...T>
std::ostream& operator<<(
std::ostream& os, const Variadic_datablock<T...>& obj)
{
print(os, obj.m_data, obj.m_id);
return os;
}
template<class Tuple, std::size_t n>
struct Reader {
static std::istream& read(
std::istream& is, Tuple& t, std::string& last_id)
{
Reader<Tuple, n - 1>::read(is, t, last_id);
auto id = extract_id(is);
if (!is_expected_id(is, id, last_id)) {
return is;
}
last_id = id;
auto type_name = extract_type_name(is);
auto expected_name =
extract_type_name(typeid(std::get<n - 1>(t)).name());
if (!is_expected_name(is, type_name, expected_name)) {
return is;
}
dischard_fill(is);
// can we do this better and extract into type of
// actual dispatched tuple?
auto s = extract_line_value_type(is);
// prefered: std::get<n - 1>(t) = s but not possible?
std::get<n - 1>(t).insert(s);
return is;
}
};
template<class Tuple>
struct Reader<Tuple, 1> {
static std::istream& read(
std::istream& is, Tuple& t, std::string& last_id)
{
auto id = extract_id(is);
if (!is_expected_id(is, id, last_id)) {
return is;
}
last_id = id;
auto type_name = extract_type_name(is);
auto expected_name =
extract_type_name(typeid(std::get<0>(t)).name());
if (!is_expected_name(is, type_name, expected_name)) {
return is;
}
dischard_fill(is);
// can we do this better and extract into the type of
// actual dispatched tuple?
// typeid(std::get<0>(t)) s = extract_line_value_type(is); ?
auto s = extract_line_value_type(is);
// prefered: std::get<0>(t) = s but not possible?
std::get<0>(t).insert(s);
return is;
}
};
template<class... Args>
std::istream& read(
std::istream& is, std::tuple<Args...>& t, std::string& last_id)
{
Reader<decltype(t), sizeof...(Args)>::read(is, t, last_id);
return is;
}
template<typename ...T>
std::istream& operator>>(std::istream& is,
Variadic_datablock<T...>& obj)
{
std::tuple<T...> tmp{};
read(is, tmp, obj.m_id);
obj.m_data = std::move(tmp);
return is;
}#pragma once
#include <iosfwd>
#include <string>
std::string extract_id(std::istream& is);
bool is_expected_id(std::istream& is,
const std::string& id, const std::string& expected_id);
std::string extract_type_name(std::istream& is);
bool is_expected_name(std::istream& is,
const std::string_view& name, const std::string_view& expected_name);
void dischard_fill(std::istream& is);
std::string extract_line_value_type(std::istream& is);
std::string erase_whitespace_in_begin(const std::string& s);
std::string extract_type_name(const std::string& typeid_result);#include "Read_from_line.h"
#include <algorithm>
#include <iostream>
#include <sstream>
std::string extract_id(std::istream& is)
{
std::string id; // id e.g. K101 PI108
std::getline(is, id, '.');
id = erase_whitespace_in_begin(id);
return id;
}
bool is_expected_id(std::istream& is,
const std::string& id, const std::string& expected_id)
{
if (expected_id == std::string{}) {
return true;
}
if (id != expected_id) {
is.setstate(std::ios::failbit);
return false;
}
return true;
}
std::string extract_type_name(std::istream& is)
{
std::string type_name; // data e.g. DeviceType
is >> type_name;
return type_name;
}
bool is_expected_name(std::istream& is,
const std::string_view& name, const std::string_view& expected_name)
{
if (name != expected_name) {
is.setstate(std::ios::failbit);
return false;
}
return true;
}
void dischard_fill(std::istream& is)
{
std::string fill; // fill ":="
is >> fill;
}
std::string extract_line_value_type(std::istream& is)
{
std::string value; // value 10
std::getline(is, value, ';');
value = erase_whitespace_in_begin(value);
return value;
}
std::string erase_whitespace_in_begin(const std::string& s)
{
std::string ret = s;
ret.erase(0, ret.find_first_not_of(" \n\r\t"));
return ret;
}
std::string extract_type_name(const std::string& typeid_result)
{
std::string ret = typeid_result;
// case normal name class Test
if (ret.find('<') == std::string::npos) {
std::istringstream ist{ ret };
ist >> ret;
ist >> ret;
return ret;
}
// Case using Test = Test2<struct TestTag>
{
std::istringstream ist{ ret };
std::getline(ist, ret, '<');
std::getline(ist, ret, '>');
}
{
std::istringstream ist2{ ret };
ist2 >> ret; // read name such as struct or class
ist2 >> ret; // get typenameTag
}
ret.erase(ret.find("Tag"));
if (ret.find("EventMask") != std::string::npos) {
std::replace(ret.begin(), ret.end(), '_', '.');
}
return ret;
}#pragma once
/*
Fake types used to test the template class.
*/
#include <iostream>
#include <string>
template<typename Tag>
struct Faketype {
std::string m;
void insert(std::string(s)) {
m = s;
}
};
template<typename Tag>
std::istream& operator>>(std::istream& is, Faketype<Tag>& obj)
{
is >> obj.m;
return is;
}
template<typename Tag>
std::ostream& operator<<(std::ostream& os, const Faketype<Tag>& obj)
{
os << obj.m;
return os;
}
using Position1 = Faketype<struct Position1Tag>;
using Position2 = Faketype<struct Position2Tag>;
using Position3 = Faketype<struct Position3Tag>;
// Only Special case. The file has EventMask.Address1
using EventMask_Address1 = Faketype<struct EventMask_Address1Tag>;
struct EnableValve
{
std::string m;
void insert(std::string(s)) {
m = s;
}
};
std::istream& operator>>(std::istream& is, EnableValve& obj)
{
is >> obj.m;
return is;
}
std::ostream& operator<<(std::ostream& os, const EnableValve& obj)
{
os << obj.m;
return os;
}请检查,按优先级排序:
Variadic_datablock class:这是一个好的设计吗?还有什么可以改进的?是否可以在std::get<n - 1>(t).insert(s);中将std::get<n - 1>(t) = s更改为Reader?请告诉我有什么气味。这是我第一次使用各种模板。我在模板中留下了一些表示气味的注释,但我不知道如何修复它们。Variadic_datablock class的单元测试:它们好吗?很容易理解?你还有什么要测试的吗?我们能摆脱protected / public黑客攻击吗?Read_from_line.h这个文件为Variadic_datablock的重载Istream操作符提供了助手函数。不要审查:
Fake_types.h:它是模拟某些数据类型的助手,因为我在我的应用程序中使用的实际数据类型更复杂,我想简化以关注模板。编辑:如果你需要更多的信息来检查这个,请告诉我。
发布于 2019-02-10 12:14:18
std::enable_if或static_assert强制执行这一要求,并结合一些模板元编程。m_id以外,我们能公开所有的事情吗?这将使这个类更容易测试。C++中的访问控制最好用于防止类不变量的破坏和隐藏复杂的内部功能.这里唯一不变的似乎是在创建之后不应该更改ID。m_data:Variadic_datablock(std::string,T.m_id{ std::move(id) },m_data{ std::move(Args)}{}print_element(os, std::get<n - 1>(t)); type_name =extract_type_name(typeid id ( std::get(t) ).name();os <<“<< id <<”。“<< type_name <<”:=“<< std::get(T) << ";Printer、Reader类以及print和read函数,所以可以将它们放在detail (或类似名称)命名空间中。typeid(x).name() 是否定义了实现?.几种不同的类型可能具有相同的名称,而且名称甚至可以在同一程序的调用之间更改。换句话说,它不是我们应该用于序列化的东西。我建议在每个元素类中添加一个static const std::string数据成员。Printer相同的问题。extract_id和is_expected_id合并为一个函数是合理的。这意味着我们不返回状态,该状态可能有效,也可能无效,然后必须将其传递到单独的函数中进行检查。坚持输入操作符约定,我们得到:std::istream& extract_id(std::istream& is, std::string& id, std::string const& expected_id);。我们可以使用流的状态来表示成功/失败,而不需要额外的布尔值。例如: std::string id;如果(!extract_id(is,id,last_id)) //检查流失败/坏位(在下一次读取时将捕获eof)返回是;extract_id函数,人们可能会想到以下情况: TEST(Test_Reader,IStreamWithValidIDAndDot) {.} TEST(Test_Reader,IStreamWithOnlyDot) {.} TEST(Test_Reader,IStreamWithIDAndNoDot) {.} TEST(Test_Reader,EmptyIStream) {.} TEST(Test_Reader,IStreamWithValidIDAndDotAndBadbitSet) {.} TEST(Test_Reader,IStreamWithValidIDAndDotAndFailitSet) {.}测试(Test_Reader,IStreamWithValidIDAndDotAndEofbitSet) {.}discard,不是dischard。FakeType::insert()和EnableValve::insert()中的额外括号: void (std::string( s) ){m= s;}按值取该参数是可以的,但是我们可以将它移到: void (std::string){m=std::move ()};operator>> FakeTypes,但我们没有使用它?last_id。https://codereview.stackexchange.com/questions/212904
复制相似问题