首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >多态TLV序列化

多态TLV序列化
EN

Code Review用户
提问于 2016-01-01 18:00:50
回答 1查看 386关注 0票数 7

这个问题的启发,我决定编写一个使用多重形态和工厂模式的替代方案。该代码适用于已实现类型的子集,即BYTEWORDSTRING,但我有以下几点考虑:

  1. 我想摆脱TLVFactory中那个丑陋的大开关。
  2. 有办法使TLVFactory::create成为TLVObject的静态成员吗?
  3. 任何其他的代码改进建议,也许是关于转换。

tlv2.cpp

代码语言:javascript
复制
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstdint>
#include <string>
#include <vector>
#include <memory>
#include <stdexcept>

class TLVObject
{
public:
    enum TLV_TYPE {UNDEFINED = 0, BYTE, WORD, DWORD, QWORD, STRING, BLOB};
    TLVObject(TLV_TYPE type) :
        m_type{type}
    {}
    virtual ~TLVObject() {}
    virtual size_t len() const { return 0; }
    virtual std::ostream &write(std::ostream &out) const { 
        writeTag(out);
        writeLen(out);
        writeData(out);
        return out;
    }
    virtual std::ostream &writeTag(std::ostream &out) const { 
        return out.put(m_type); 
    }
    virtual std::ostream &writeLen(std::ostream &out) const {
        return out.put(len());
    }
    virtual std::ostream &writeData(std::ostream &out) const {
        return out;
    }
    virtual std::istream &read(std::istream &in) {
        readTag(in);
        readLen(in);
        readData(in);
        return in;
    }
    virtual std::istream &readTag(std::istream &in) { 
        char ch;
        in.get(ch);
        m_type = static_cast<TLV_TYPE>(ch);
        return in;
    }
    virtual std::istream &readLen(std::istream &in) { return in; }
    virtual std::istream &readData(std::istream &in) { return in; }
private:
    TLV_TYPE m_type;
};

class TLVByte : public TLVObject
{
public:
    TLVByte(uint8_t value) :
        TLVObject{BYTE},
        m_value{value}
    {}
    TLVByte() : TLVByte{0} {}
    virtual ~TLVByte() {}
    virtual size_t len() const { return sizeof(m_value); }
    virtual std::ostream &writeData(std::ostream &out) const { 
        return out.put(m_value); 
    }
    virtual std::istream &readLen(std::istream &in) {
        if (len() != static_cast<size_t>(in.get())) {
            std::out_of_range("TLV len error");
        } 
        return in;
    }
    virtual std::istream &readData(std::istream &in) { 
        m_value = in.get(); return in; 
    }
private:
    uint8_t m_value;
};

class TLVWord : public TLVObject
{
    public:
    TLVWord(uint16_t value) :
        TLVObject{WORD},
        m_value{value}
    {}
    TLVWord() : TLVWord{0} {}
    virtual ~TLVWord() {}
    virtual size_t len() const { return sizeof(m_value); }
    virtual std::ostream &writeData(std::ostream &out) const { 
        return out.put(m_value >> 8).put(m_value & 0xff);
    }
    virtual std::istream &readLen(std::istream &in) {
        if (len() != static_cast<size_t>(in.get())) {
            std::out_of_range("TLV len error");
        } 
        return in;
    }
    virtual std::istream &readData(std::istream &in) { 
        m_value = in.get() << 8; 
        m_value |= in.get();
        return in; 
    }
private:
    uint16_t m_value;
};


class TLVString : public TLVObject
{
public:
    TLVString(std::string str) :
        TLVObject{STRING},
        m_value{str}
    {}
    TLVString() : TLVString("") {}
    virtual ~TLVString() {}
    virtual size_t len() const { return m_value.size(); }
    virtual std::ostream &writeData(std::ostream &out) const { 
        return out << m_value;
    }
    virtual std::istream &readLen(std::istream &in) {
        m_value.reserve(in.get());
        return in;
    }
    virtual std::istream &readData(std::istream &in) { 
        for (int i = m_value.capacity(); i; --i) {
            m_value += in.get();
        }
        return in; 
    }
private:
    std::string m_value;
};

class TLVFactory
{
public:
    static std::unique_ptr<TLVObject> create(TLVObject::TLV_TYPE type) {
        switch (type) {
            case TLVObject::BYTE: 
                return std::make_unique<TLVByte>();
                break;
            case TLVObject::WORD: 
                return std::make_unique<TLVWord>();
                break;
            case TLVObject::STRING: 
                return std::make_unique<TLVString>();
                break;
            case TLVObject::DWORD: 
            case TLVObject::QWORD: 
            case TLVObject::BLOB:
            default:
                std::out_of_range("Can't construct object of that type");
        }
        return std::make_unique<TLVObject>(TLVObject::UNDEFINED);
    }
};

class TLVCollection : public std::vector<std::unique_ptr<TLVObject>> {
public:
    void write(std::ostream &out) const {
        for (const auto &item : *this) {
            item->write(out);
        }
    }
    void read(std::istream &in) {
        while (in.good() && in.peek() > 0) {
            push_back(TLVFactory::create(static_cast<TLVObject::TLV_TYPE>(in.peek())));
            back()->read(in);
        }
    }
};

简单测试代码

代码语言:javascript
复制
int main()
{
    TLVCollection vec;
    vec.push_back(std::make_unique<TLVByte>(TLVByte{7}));
    vec.push_back(std::make_unique<TLVString>(TLVString{"banana"}));
    vec.push_back(std::make_unique<TLVWord>(TLVWord{0x1234}));
    std::ofstream out("tlvtest.bin");
    vec.write(out);
    out.close();

    TLVCollection v2;
    std::ifstream in("tlvtest.bin");
    v2.read(in);
    in.close();
    v2.write(std::cout);
}

结果

当我运行该程序并通过xxd将其输出管道(它只是将二进制输入转换为十六进制表示)时,我得到如下结果:

./tlv2 2区xxd 00000000: 0101 0705 0662 616 e 616 e 6102 0212 .....banana...4

EN

回答 1

Code Review用户

回答已采纳

发布于 2016-01-02 00:22:30

以下是一些建议。

在适当的情况下使用const

在构造函数中,只需复制传入的值,而不对其进行变异。它应该声明为const,因为它没有发生变异。

不允许构造无效对象

我不明白允许调用方创建未定义的TLV类型的意义。它有0的长度,所以没有什么可写或读的。最好是根本不允许这样的东西被建造。我会从枚举中删除UNDEFINED值。(使基类的构造函数受到保护,这样就不会直接在子类之外实例化。)

在适当的

中使用纯虚拟方法

如果TLVObject使virtual成为必须被子类覆盖的方法,编译器将阻止调用者构造基本对象。因此,我会让len()writeData()readLen()readData()都是纯虚拟的:

代码语言:javascript
复制
virtual size_t len() const = 0;
...
virtual std::ostream &writeData(std::ostream &out) const = 0;
...
virtual std::istream &readLen(std::istream &in) = 0;
virtual std::istream &readData(std::istream &in) = 0;

在重写的方法

上使用override

当重写子类中的方法时,应将其标记为override,以便编译器知道您的意图,并警告您,如果基类中的方法签名发生了更改,或者在子类中拼写错误。所以看起来是这样:

代码语言:javascript
复制
class TLVByte : public TLVObject
{
...
    virtual size_t len() const override { return sizeof(m_value); }
    virtual std::ostream &writeData(std::ostream &out) const override { 
        return out.put(m_value); 
    }
    virtual std::istream &readLen(std::istream &in) override {
        if (len() != static_cast<size_t>(in.get())) {
            std::out_of_range("TLV len error");
        } 
        return in;
    }
    virtual std::istream &readData(std::istream &in) override { 
        m_value = in.get(); return in; 
    }

..。

工厂方法

总的来说,你的工厂方法看起来是完全合理的。我最初说的是,“没有理由它不可能是TLVObject中的一个公共静态方法,您只需要转发声明子类”:

代码语言:javascript
复制
class TLVByte;
class TLVWord;
class TLVString;

class TLVObject
{
public:
    enum TLV_TYPE {UNDEFINED = 0, BYTE, WORD, DWORD, QWORD, STRING, BLOB};
    static std::unique_ptr<TLVObject> create(TLVObject::TLV_TYPE type) {
        switch (type) {
            case TLVObject::BYTE: 
                return std::make_unique<TLVByte>();
                break;
            case TLVObject::WORD: 
                return std::make_unique<TLVWord>();
                break;
            case TLVObject::STRING: 
                return std::make_unique<TLVString>();
                break;
            case TLVObject::DWORD: 
            case TLVObject::QWORD: 
            case TLVObject::BLOB:
            default:
                std::out_of_range("Can't construct object of that type");
        }
        return std::make_unique<TLVObject>(TLVObject::UNDEFINED);
    }
...

然而,我错了。由于试图使用尚未完全描述的类型作为模板(std::unique_ptr<T>)的参数,上面的内容将无法工作,这是行不通的。

另一个选择是让它成为一个独立的功能。虽然这样做没有问题,但没有理由需要自己在一个类中。

正如您可能从上述错误中猜测到的,我不是模板专家。因此,有一个问题--是否有可能使create()方法成为模板化的函数?类似于:

代码语言:javascript
复制
template<T>
static unique_ptr<T> create()
{
    return std::make_unique<T>();
}

或者干脆摆脱工厂函数,只使用std::make_unqiue<Whichever>();创建它们?

另外,您的create()的最后一个返回语句将永远不会到达,因为您有一个default case。正如上面提到的,它不应该是必要的,因为您不应该允许构造一个未定义的对象。

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

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

复制
相关文章

相似问题

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