Search code examples
c++c++11shared-ptr

Recognize derived class with identifier not working


I am trying to create a general interface for derived classes using unique identifier numbers in C++.

This is what my code looks like (you need at least C++11 to compile it):

#include <iostream>
#include <memory>

class Base
{
    protected:
        int ident;
        Base(int newIdent) : ident(newIdent) { }

    public:
        Base() : ident(0x01) { }
        virtual ~Base() { }           //needed to make class polymorphic
        int getIdent() { return ident; }
};

class Derived : public Base
{
    protected:
        int answer;
        Derived(int newIdent, int newAnswer) : Base(newIdent), answer(newAnswer) { }

    public:
        Derived(int newAnswer) : Base(0x11), answer(newAnswer) { }
        int getAnswer() { return answer; }
};

int main()
{
    std::shared_ptr<Base> bPtr = std::make_shared<Derived>(Derived(42));

    std::cout << "ident = 0x" << std::hex << bPtr->getIdent() << std::dec << "\n";
    if(bPtr->getIdent() & 0xF0 == 1)
    {
        std::shared_ptr<Derived> dPtr = std::dynamic_pointer_cast<Derived>(bPtr);
        std::cout << "answer = " << dPtr->getAnswer() << "\n";
    }

    return 0;
}

Of course you should expect that the program outputs ident = 0x11 and answer = 42, but it doesn't, because it exists normally after the ident = 0x11 line. I also did some examination with GDB and the disassembly of the critical if condition check in the main function confirms the issue:

   0x0000000000400f46 <+196>:   call   0x401496 <std::__shared_ptr<Base, (__gnu_cxx::_Lock_policy)2>::operator->() const>
   0x0000000000400f4b <+201>:   mov    rdi,rax
   0x0000000000400f4e <+204>:   call   0x4012bc <Base::getIdent()>
   0x0000000000400f53 <+209>:   mov    eax,0x0
   0x0000000000400f58 <+214>:   test   al,al
   0x0000000000400f5a <+216>:   je     0x400fb7 <main()+309>

When you break at *0x400f53, rax nicely keeps the correct value (0x11), but the following instruction just overwrites rax with zero, test sets the zero flag and the je instruction jumps to the end of the main function because the zero flag is set. What is going on here? Am I missing something or is the compiler (g++ 4.9.2 with x86_64-linux-gnu target) generating wrong instructions?


Solution

  • Always compile with warnings enabled. Your problem is an order of operations issue where & has lower precedence than ==, so you need:

    if ((bPtr->getIdent() & 0xF0) == 1)
    

    Although then you're comparing against the wrong thing, so you really want:

    if ((bPtr->getIdent() & 0xF0) == 0x10)
    

    In this case, you would see this from gcc:

    main.cpp:32:32: warning: suggest parentheses around comparison in operand of '&' [-Wparentheses]
         if(bPtr->getIdent() & 0xF0 == 1)
                               ~~~~~^~~~
    

    or this from clang:

    main.cpp:32:25: warning: & has lower precedence than ==; == will be evaluated first [-Wparentheses]
        if(bPtr->getIdent() & 0xF0 == 1)
                            ^~~~~~~~~~~
    main.cpp:32:25: note: place parentheses around the '==' expression to silence this warning
        if(bPtr->getIdent() & 0xF0 == 1)
                            ^
                              (        )
    main.cpp:32:25: note: place parentheses around the & expression to evaluate it first
        if(bPtr->getIdent() & 0xF0 == 1)
                            ^
           (                      )