首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >可变数据库锁

可变数据库锁
EN

Code Review用户
提问于 2019-02-05 09:43:21
回答 1查看 274关注 0票数 4

我想让我的各种模板类进行检查。

首先解释一下它应该做什么:

我正在编写一个应用程序,在其中读取块数据。

在我阅读数据之前,我知道哪些格式是可以期待的。

例如:

代码语言:javascript
复制
   ID123.EnableValve := FALSE; 
   ID123.Position1 := 0; 
   ID123.Position2 := 2; 
   ID123.Position3 := 9; 

或者:

代码语言:javascript
复制
   UDT30.EnableValve := FALSE; 
   UDT30.This := 0; 
   UDT30.is := L#2; 
   UDT30.an := 9; 
   UDT30.Example := TRUE; 

这些数据块有以下相似之处:

  • 每一行都以id开头(例如,UDT30ID123)。
  • 此设备Id必须在每一行上相同。否则,我们读取一个损坏的数据块。
  • 然后是一个类型名称(例如EnableValvePosition3)。
  • 类型名称是唯一的。这意味着,在一个数据块中,永远不会有两次相同的类型(例如,Position1不会发生两次)。
  • 然后,字符串:=总是跟随。
  • 添加后面的值(例如,TRUE0L#)。
  • 行的计数是灵活的(例如长度= 45)。

我想对数据库锁执行以下操作:

  1. 在数据库锁中读取(从std::istream)
  2. 访问特定行并修改其值(例如,ID123.EnableValve := FALSE;变为ID123.EnableValve := TRUE;
  3. 将数据库写回(到std::ostream)

此外,还必须能够:

  1. 构造一个新的类型数据库
  2. 编写数据库锁(到std::osstream)

我希望继承此模板,并限制对行的set和get的使用。继承的类可以使用受保护的集/获取来决定要修改哪些行,以及哪些行是只读的。

我使用Google和Visual2017创建了模板的单元测试。在这里,您可以看到模板可以做什么。

Variadic_templatesTest.cpp

代码语言:javascript
复制
#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);
}

Variadic_datablock.h

代码语言:javascript
复制
#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;
}

Read_from_line.h

代码语言:javascript
复制
#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);

Read_from_line.cpp

代码语言:javascript
复制
#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;
}

Fake_types.h

代码语言:javascript
复制
#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:它是模拟某些数据类型的助手,因为我在我的应用程序中使用的实际数据类型更复杂,我想简化以关注模板。

编辑:如果你需要更多的信息来检查这个,请告诉我。

EN

回答 1

Code Review用户

回答已采纳

发布于 2019-02-10 12:14:18

Variadic_datablock:

  • 类Variadic_datablock /*需要T中的元素。是唯一的(不重复类型) */我们可以使用类中的std::enable_ifstatic_assert强制执行这一要求,并结合一些模板元编程。
  • 应该允许一个空参数包吗?看起来这会破坏print / read函数,所以我们可能也应该检查一下。
  • 受保护成员函数的目的是什么?我们是否真的需要继承,或者除了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) << ";
  • 由于用户不应该直接使用PrinterReader类以及printread函数,所以可以将它们放在detail (或类似名称)命名空间中。
  • typeid(x).name() 是否定义了实现?.几种不同的类型可能具有相同的名称,而且名称甚至可以在同一程序的调用之间更改。换句话说,它不是我们应该用于序列化的东西。我建议在每个元素类中添加一个static const std::string数据成员。

阅读器:

  • Printer相同的问题。
  • extract_idis_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
票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

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

复制
相关文章

相似问题

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