1

我对给定项目的目标是查找和解析特定的串行数据包。好消息是已经编写了一个通用的数据包类来处理大部分繁重的工作。但是,我想提高类的性能,如下所示。如果有些语法有点不对,请原谅,我从来都不擅长从内存中记住 C++ 语法...... :(

class GenericPacket {
 public:
  GenericPacket();  // does nothing except initialize member variables
  ~GenericPacket();
  GenericPacket(const GenericPacket& other);
  GenericPacket& operator=(const GenericPacket& other);
  GenericPacket(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock);
  Parse(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock);
  // "get" functions go here...
  //  ... 

 protected:
  // the functions below are called by Parse()
  ParseHeader(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock);
  ParseData(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock);
  ParseCheckSum(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock);

 private:
  // member variables go here...

该类的基本功能是读入已排队的数据流,并将其处理成数据包的各个组成部分。它还检查它从队列中剥离的数据包的有效性。还有另外两个构造函数和相应的“解析”函数,它们与调用者在构造过程中不修改队列的情况相关联,还有另一个使用简单数组而不是队列的版本。这两个都只是上面显示的 Parse() 函数调用的包装器。另外,请注意,调用者可以使用默认构造函数并手动调用 Parse,也可以调用非默认构造函数,该构造函数将尝试通过使用来自第一个解析数据包的数据填充成员变量来使对象“有用”。还,请注意,此类对在 ParseData 调用中找到的数据进行解码没有任何作用。它只是将原始十六进制存储在 uint8_t 数组中。

现在,有了这些背景信息,我当前的问题是寻找一个高度特定的数据包,该数据包可能占所有流量的 2%。另外,我希望能够解码数据,这将添加更多的成员变量。像这样的东西:

class HighlySpecificPacket {
 public:
  HighlySpecificPacket();
  // non-default constructor that calls parse
  HighlySpecificPacket(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock, int* status);
  ~HighlySpecificPacket ();
  // copy constructor and the like...
  // ...
  Parse(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock, int* status);
  // ...
 private:
  double real_data;
  // etc...

基本上,HighlySpecificPacket 类中的 Parse() 函数可以利用 GenericPacket 中的许多受保护函数。它可以使用这些函数,但是在 ParseHeader() 之后当它注意到数据包没有我专门寻找的数据包的签名时停止处理。此外, int* status 可用于向调用者发回可以有效忽略的字节数的信号(从而避免生产者线程不必要地推入队列)。此外,(我应该在前面提到这一点),GenericPacket 中的受保护函数应该并且需要从用户那里抽象出来,因为它们总是由 GenericPacket 的 Parse() 调用。

总的来说,我迷失了前进的最佳方式。我不希望 GenericPacket 的所有功能都暴露给客户端(即非线程安全构造函数),但由于基类中的受保护函数,我可以通过继承重用大量代码。我也可以根据需要修改 GenericPacket 类。对我来说,这显然也是一种“is-a”关系,但我不知道如何实现这一点的机制。我想使用私有继承,但我多次被告知这是不好的做法,只是一个创可贴修复。

总的来说,我遇到了以下问题(请原谅这些问题,因为我上次积极使用继承是在我回到学校的时候......):

  1. 解决这个问题的最佳方法是什么?如果我使用组合,我将无法访问我想要重用的功能。但是众所周知,使用私有继承很麻烦,特别是因为我必须手动编写包装函数来公开我希望客户端使用的基类部分(即“getter”函数)......

  2. 有没有办法防止dynamic_casts,或者拦截它们?在这种情况下,从派生类转换为基类是有意义的。但是,仅当标头与我的特定数据包的签名匹配时,才应该从基类转换为派生类。

  3. 包含一个将基类作为派生类参数的构造函数是否有意义?这个有什么特别的名字吗?这就是我的想法: DerivedClass& DerivedClass(const BaseClass& base); 本质上,我会检查标头签名,然后仅在标头签名与特定数据包的情况匹配的情况下完成构造。

  4. 现在我通过询问从基础到派生的强制转换和构造函数打开了一罐蠕虫......那么等式/不等式/赋值运算符等呢?我是否必须编写每个案例的具体案例来检查派生与基础?例如,如果在执行以下操作时所有基类元素都相同,我可以返回“true”:

    如果(基础==派生)

    怎么样的东西:

    derived = base;  // take in all elements from base and attempt to construct it as a specific case of the base class
    
  5. 如果派生类只有双精度/整数/等,我什至需要担心派生类的复制构造函数/赋值运算符吗?(没有指针/动态内存)?由于动态分配的内存,基类具有复制构造函数和赋值运算符。默认派生复制构造函数不只是调用基本复制构造函数吗?

感谢所有的帮助。我知道我的帖子有很多内容需要吸收,所以我很感激你的耐心。我在想,除了我在学校学到的形状、矩形、正方形、圆形等示例之外,我偶然发现了第一个使用继承的“真实”案例。再次感谢。

为清楚起见,编辑添加:

这就是我想要访问 ParseHeader() 等函数的原因:

在通用包中:

Parse(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock) {
  ParseHeader(data_stream, the_lock);
  ParseData(data_stream, the_lock);  // generic, only an array of hex
  ParseCRC(data_stream, the_lock);  //determines validity
}

在高度特定的数据包中:

Parse(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock, int* status) {
  ParseHeader(data_stream, the_lock);
  // do checks here to see if the packet is actually the kind I want
  if (header_ == WHAT_I_WANT) {
    ParseData(data_stream, the_lock);
    ParseCRC(data_stream, the_lock);
  } else {
    *status = header_.packet_length_;  // number of bytes to ignore.
  }
}
4

2 回答 2

0
class GenericPacket
{
public:
    static std::shared_ptr<GenericPacket> fromStream(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock)
    {
         // extract the header and return std::make_shared<MySpecificPacket>(...) where you've chosen MySpecificPacket accordingly
    }

    bool parse(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock)
    {
        return this->parse_(data_stream, the_lock);
    }

protected:
    // override this method for specific implementations of parse, can also be made abstract if there is no default behaviour
    virtual bool parse_(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock);
};

class MySpecificPacket: public GenericPacket
{
protected:
    bool parse_(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock)
    {
         // do something specific, including decoding the data if you need to
    }
};

以及客户端(生产者)类如何使用它:

std::shared<GenericPacket> packet = GenericPacket::fromStream(stream, lock);
if (packet->parse(stream, lock))
{
    // push onto event queue...
}
于 2013-06-25T07:54:50.603 回答
0

感谢评论中的精彩讨论,这是我正在考虑实施的一个提议的解决方案,它在我洗澡的时候突然袭击了我(顺便说一句,其他人发现最好的想法是在淋浴时完成的吗?):

namespace packets {
ParseHeader(...);
ParseData(...);
ParseCheckSum(...);

class GenericPacket {
  // same stuff as before, except the scope of the "protected" functions...
};

class HighlySpecificPacket {
  // same stuff as before...
  // new stuff:
 public:
  // will probably have to add wrappers here to expose
  //   some GenericPacket member variables...
 private:
  GenericPacket packet;  // composition for all the necessary member variables

我认为这是只向客户公开我想要的东西的好方法,除了我现在必须让他们直接访问 ParseHeader/ParseData/ParseChecksum 函数的事实,这是我希望避免的(这是为什么它们最初是受保护的。此外,如果我将这些功能公开,我仍然可以使用组合,但我的问题仍然是我想让 HighSpecificPacket 可以访问这些功能,而不让 HighSpecificPacket 的用户可以访问它们。

这种方法的优点是它可以减轻很多 dynamic_cast 并将一种类型等同于我遇到的另一种问题......

于 2013-06-25T07:29:31.117 回答