Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ design: cast from base to derived class with no extra data members

I write quite a lot of code which processes message protocols. Quite often a message protocol will have a generic message frame which can be deserialised from a serial port or socket; the frame contains a message type, and the message payload must be processed based on the message type.

Normally I write a polymorphic set of classes with accessor methods and a constructor which takes a reference to the message frame.

It occurs to me though that instead of constructing an accessor class based on a reference to the message frame, I could just derive the accessor classes directly from the message frame, and then reinterpret_cast from the message frame to the appropriate accessor class. This makes the code more concise and saves some bytes and processor cycles.

See the (extremely contrived and condensed) example below. Obviously for production code this would all need to be properly encapsulated, the cast made a member of the derived class, better separation of concerns imposed, and some validation added. This has all been removed for the sake of putting together a concise example.

#include <iostream>
#include <cstring>
#include <vector>

struct GenericMessage
{
  GenericMessage(const char* body):body_(body, body+strlen(body)){}
  std::vector<char> body_;  
};

struct MessageType1:public GenericMessage
{
    int GetFoo()const
    {
        return body_[2];
    }
    int GetBar()const
    {
        return body_[3];
    }    
};

int main() 
{
    GenericMessage myGenericMessage("1234");
    MessageType1* myMgessageType1 = reinterpret_cast<MessageType1*>(&myGenericMessage);
    std::cout << "Foo:" << myMgessageType1->GetFoo() << std::endl;
    std::cout << "Bar:" << myMgessageType1->GetBar() << std::endl;
    return 0;
}

I've never see this done anywhere. Is there any downside to casting from base to derived in this way, given that derived has no additional data members?

like image 557
Simon Elliott Avatar asked Dec 11 '13 11:12

Simon Elliott


People also ask

Can you cast from base class to derived?

So, downcasting from a base to a derived class is not possible because data members of the inherited class are not allocated.

Why downcasting is not allowed?

Downcasting is not allowed without an explicit type cast. The reason for this restriction is that the is-a relationship is not, in most of the cases, symmetric. A derived class could add new data members, and the class member functions that used these data members wouldn't apply to the base class.

Which members of a base class are never be accessed in derived?

4. Which members can never be accessed in derived class from the base class? Explanation: There is no restriction for a derived class to access the members of the base class until and unless the members are private. Private member are declared so that those members are not accessible outside the class.

Which type of members can't be accessed in derived classes of a base class all can be accessed protected private public?

Which type of members can't be accessed in derived classes of a base class? Explanation: The private members can be accessed only inside the base class. If the class is derived by other classes. Those members will not be accessible.


3 Answers

It's fine enough in many applications, if you add the following test:

static_assert(
    sizeof(MessageType1) == sizeof(GenericMessage),
        "Cannot overlay MessageType1 upon GenericMessage." );

No compiler optimization will change the layout of the base-type slice of the derived type, so this is usually safe enough.

Also, use static_cast. reinterpret_cast is for things more perverse than this.


...OK, yes, yes, this can fail when all of the following are true:

  • GenericMessage has padding at the end.
  • A member (later added) in MessageType1 happens to lay inside that padding.
  • You send the overlaid MessageType1 through code-paths that read from that padding-zone before writing to it.

So weigh expediency vs robustness, and then do what you think is best. You're not the first person to use this pattern, and it's not taboo, despite the hand-wringing in the other answers here -- although they are certainly correct that it has special hazards.

like image 74
Keith Russell Avatar answered Sep 22 '22 05:09

Keith Russell


Here is why I would not use this technique:

  1. It is a violation of the Standard and causes the behavior to be undefined. It is probably true that this works nearly all the time, but you can't rule out problems in the future. Compilers have been seen to make use of undefined behavior in optimizations, much to the disadvantage of the unsuspecting programmer. And you can't predict when and under what circumstances this will happen.

  2. You can't guarantee that neither you nor a team mate will ever add some data members to the derived type. Your class hierarchy will grow and more code will be added over time; at some point it may not be obvious to you or another programmer that adding an innocent data member to the derived type (even temporarily, perhaps for some debugging purpose) can spell disaster.

  3. There are clean and legal alternatives, for example using wrappers based on references:

    #include <iostream>
    
    struct Elem
    { };
    
    struct ElemWrapper
    {
      Elem &elem_;
    
      ElemWrapper(Elem &elem) : elem_(elem)
      { }
    };
    
    struct ElemWrapper1 : ElemWrapper
    {
      using ElemWrapper::ElemWrapper;
    
      void foo()
      { std::cout << "foo1" << std::endl; }
    };
    
    struct ElemWrapper2 : ElemWrapper
    {
      using ElemWrapper::ElemWrapper;
    
      void foo()
      { std::cout << "foo2" << std::endl; }
    };
    
    int main()
    {
      Elem e;
    
      ElemWrapper1(e).foo();
    
      return 0;
    }
    
like image 41
jogojapan Avatar answered Oct 17 '22 03:10

jogojapan


No you can't !

It may work in your case but it's not advisable since (quick explanation) derived class may have more members or virtual functions, which would not be available from the base.

The easiest solution is to keep your inheritance scheme (which is good) but use a factory to instantiate the correct message type. Example :

struct GenericMessage* create_message(const char* body) {
   int msg_type = body[5]; // I don't know where type is coded, this is an example
   switch(msg_type) {
   case 1:
      return new MessageType1(body);
      break;
   // etc.

You can then safely dynamic_cast it later.

Note that you can put your factory anywhere, for example in the GenericMessage class itself, i.e.

GenericMessage myGenericMessage("1234");
MessageType1* myMgessageType1 = myGenericMessage.get_specialized_message();

Alternatively, you can also build a specialized message from a base one, but it's the same at the end :

GenericMessage myGenericMessage("1234");
MessageType1* myMgessageType1 = new MessageType1( myGenericMessage );
like image 2
Offirmo Avatar answered Oct 17 '22 01:10

Offirmo