C ++继承/类设计问题

时间:2013-06-25 06:28:00

标签: c++ inheritance friend composition

我对给定项目的目标是查找和解析特定的串行数据包。好消息是已经编写了一个通用的数据包类来处理大部分繁重的工作。但是,我想改进课程的表现,如下所示。如果一些语法稍微偏离,请原谅我,我从未擅长从内存中记住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...

该类的基本功能是读入已排队的数据流并将其处理到数据包的各个组件中。它还会检查从队列中剥离的数据包的有效性。还有两个构造函数和相应的&#34; Parse&#34;与在调度期间调用者不修改队列的情况相关联的函数以及使用简单数组而不是队列的另一个版本。这两个都只是上面显示的Parse()函数调用的包装器。另外,请注意调用者可以使用默认构造函数并手动调用Parse,也可以调用非默认构造函数,该构造函数将尝试使对象&#34;有用&#34;通过用第一个解析的数据包中的数据填充成员变量。另请注意,此类不会对它在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类。对我而言,这显然是一个&#34; is-a&#34;关系也是如此,但我对如何实现这一目标的机制感到迷茫。我想使用私人继承,但我已多次被告知这是一个糟糕的实践,只是一个创可贴。

总的来说,我被困住了,并且有以下问题(请原谅,如果这些都是自从我最后一次积极使用继承以来就是我的回归...)

  1. 最好的解决方法是什么?如果我使用合成,我将无法访问我想要重用的函数。但是使用私有继承被认为是hacky,特别是因为我必须手动编写包装函数来公开我希望客户端使用的基类部分(即&#34; getter&#34;函数)...

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

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

  4. 现在我通过询问从基础到派生的转换和构造函数来打开一堆蠕虫...等于/不等式/赋值运算符等等?我是否必须编写每个特定案例来检查派生与基础?例如,我可以返回&#34; true&#34;如果在执行以下操作时所有基类元素都相同:

    if(base == derived)

    如下:

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

  6. 感谢所有帮助。我知道我的帖子有很多内容,所以我很感激耐心。我以为我偶然发现了第一个&#34;真正的&#34;除了我在学校学到的形状,矩形,方形,圆形等的例子之外,我还要使用继承。再次感谢。

    为了清晰起见编辑添加:

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

    在GenericPacket中:

    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
    }
    

    在HighlySpecificPacket中:

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

2 个答案:

答案 0 :(得分: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函数的事实,这是我希望避免的(这就是为什么他们最初受到保护。另外,如果我公开这些功能,我仍然可以使用合成,但我的问题仍然是我想让HighlySpecificPacket可以访问这些功能,而不让HighlySpecificPacket的用户有权访问它们。

这种方法的优点在于它可以减轻很多dynamic_cast并将一种类型等同于我所拥有的另一个问题......

答案 1 :(得分: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...
}