【问题标题】:C++ Inheritance/Class Design IssueC++ 继承/类设计问题
【发布时间】:2013-06-25 06:27:59
【问题描述】:

对于给定项目,我的目标是查找和解析特定的串行数据包。好消息是已经编写了一个通用的数据包类来处理大部分繁重的工作。但是,我想提高类的性能,如下所示。如果有些语法有点不对,请原谅,我从来都不擅长从记忆中记住 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. 有没有办法阻止或拦截动态转换?在这种情况下,从派生类转换为基类是有意义的。但是,只有当标头与我的特定数据包的签名匹配时,才应该从基类转换为派生类。

  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.
  }
}

【问题讨论】:

  • 好问题。你确定你需要指针和引用不能完成这项工作吗?
  • 我团队的风格指导不喜欢非常量指针,而且我在搞乱一些东西,所以我使用指针。
  • "我想使用私有继承" -- 为什么?众所周知,HighlySpecificPacket 是一种 GenericPacket。见stackoverflow.com/a/656235/544557
  • 如果你正在解析一个流来决定它是什么类型的数据包,你正在实现一个工厂。您的 Parse 方法应该是静态的。注意:DerivedClass&amp; DerivedClass(const BaseClass&amp; base); -- 构造函数不返回值。 then only complete construction -- 停止构造的唯一方法是抛出异常。研究工厂……这就是你需要的概念。
  • 听起来你有点想多了。封装变化的东西。封装保持不变的东西。从后者委托给前者。还要先编写最简单的特定案例测试,例如什么都不解析,然后再解析一点,然后是专门的数据。泛化您的代码,仅将专业化移动到它自己的类中。

标签: c++ inheritance friend composition


【解决方案1】:
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...
}

【讨论】:

    【解决方案2】:

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

    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 并将一种类型等同于我遇到的另一种问题......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-06
      • 2023-01-12
      • 1970-01-01
      • 2012-05-11
      • 1970-01-01
      • 1970-01-01
      • 2012-10-11
      相关资源
      最近更新 更多