Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Special Interest Groups
  3. C++ Gurus
  4. Calling methods on C++ classes that are null

Calling methods on C++ classes that are null

Scheduled Pinned Locked Moved Solved C++ Gurus
9 Posts 4 Posters 2.4k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • fcarneyF Offline
    fcarneyF Offline
    fcarney
    wrote on last edited by fcarney
    #1

    Yes, this is a very bad idea. But I observed some interesting things about QObject and C++ classes in general.
    If I call deleteLater on a null QObject pointer it does not crash. It spews errors in the console. So I was wondering what deleteLater was actually doing:
    void QObject::deleteLater()
    {
    QCoreApplication::postEvent(this, new QDeferredDeleteEvent());
    }

    As long as the call doesn't access data members or other functions that do it seems to work fine. This may in fact be compiler dependent however. Calling methods on null objects is undefined from what I have read.

    So I wondered can we detect if a method is being called on a null object and prevent a crash?

    class NoCrashOnNull
    {
    public:
        NoCrashOnNull()
            : value(42)
        {
    
        }
    
        int someMethod(){
            qDebug() << this;
            auto ptr = this;
            qDebug() << ptr;
            if(ptr){
                qDebug() << this << "Pointer was null";
                return -1;
            }
    
            return value;
        }
    
    protected:
        int value;
    };
    

    This will not work. The compiler optimizes out the logic branch because "this" is "always" not null. But I found if I call a method to check for null and return a boolean I can detect null and it will branch:

    class NoCrashOnNull
    {
    public:
        NoCrashOnNull()
            : value(42)
        {
    
        }
    
        int someMethod(){
            qDebug() << this;
            auto ptr = this;
            qDebug() << ptr;
            if(checkNull(this)){
                qDebug() << this << "Pointer was null";
                return -1;
            }
    
            return value;
        }
    
        static bool checkNull(void* vptr){
            return vptr ? true : false;
        }
    protected:
        int value;
    };
    

    This is gcc in Linux. It may not be the same story for other versions and other compilers. I just wanted to play a bit.

    C++ is a perfectly valid school of magic.

    JonBJ 1 Reply Last reply
    0
    • fcarneyF fcarney

      Yes, this is a very bad idea. But I observed some interesting things about QObject and C++ classes in general.
      If I call deleteLater on a null QObject pointer it does not crash. It spews errors in the console. So I was wondering what deleteLater was actually doing:
      void QObject::deleteLater()
      {
      QCoreApplication::postEvent(this, new QDeferredDeleteEvent());
      }

      As long as the call doesn't access data members or other functions that do it seems to work fine. This may in fact be compiler dependent however. Calling methods on null objects is undefined from what I have read.

      So I wondered can we detect if a method is being called on a null object and prevent a crash?

      class NoCrashOnNull
      {
      public:
          NoCrashOnNull()
              : value(42)
          {
      
          }
      
          int someMethod(){
              qDebug() << this;
              auto ptr = this;
              qDebug() << ptr;
              if(ptr){
                  qDebug() << this << "Pointer was null";
                  return -1;
              }
      
              return value;
          }
      
      protected:
          int value;
      };
      

      This will not work. The compiler optimizes out the logic branch because "this" is "always" not null. But I found if I call a method to check for null and return a boolean I can detect null and it will branch:

      class NoCrashOnNull
      {
      public:
          NoCrashOnNull()
              : value(42)
          {
      
          }
      
          int someMethod(){
              qDebug() << this;
              auto ptr = this;
              qDebug() << ptr;
              if(checkNull(this)){
                  qDebug() << this << "Pointer was null";
                  return -1;
              }
      
              return value;
          }
      
          static bool checkNull(void* vptr){
              return vptr ? true : false;
          }
      protected:
          int value;
      };
      

      This is gcc in Linux. It may not be the same story for other versions and other compilers. I just wanted to play a bit.

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by JonB
      #2

      @fcarney
      Till the compiler decides to inline/optimize/analyze your method call, see you're calling it with this, and puts you back in the first example situation :) But yes in practice (not "aggressive" optimization) passing a parameter to a function tends to do what you say. When debugging and having hassle from the optimizer such that I can't correctly view certain variables' values in Watch window etc., passing them to a dummy function seems to let you see their values correctly.

      What I don't get is that when I was still C and a work colleague was C++ I distinctly remember him showing me that you could call a method on a null instance, and check for this == nullptr/0 and write code for that, even though it seemed strange. Has C++ always forbidden calling member methods on null instances, did it used to allow it or not enforce it, or what?

      fcarneyF 1 Reply Last reply
      1
      • JonBJ JonB

        @fcarney
        Till the compiler decides to inline/optimize/analyze your method call, see you're calling it with this, and puts you back in the first example situation :) But yes in practice (not "aggressive" optimization) passing a parameter to a function tends to do what you say. When debugging and having hassle from the optimizer such that I can't correctly view certain variables' values in Watch window etc., passing them to a dummy function seems to let you see their values correctly.

        What I don't get is that when I was still C and a work colleague was C++ I distinctly remember him showing me that you could call a method on a null instance, and check for this == nullptr/0 and write code for that, even though it seemed strange. Has C++ always forbidden calling member methods on null instances, did it used to allow it or not enforce it, or what?

        fcarneyF Offline
        fcarneyF Offline
        fcarney
        wrote on last edited by
        #3

        @JonB What is weird is the qDebug() << this and qDebug << ptr both print 0x0. So I can view that this is null, but when it was making decisions based upon this and ptr it assumed it cannot be null and didn't branch. Somehow the function call sidesteps this. Yeah, I have not tried a higher level of optimization.

        I think in the early C++ days the compilers were a lot less smart. So the scenario you describe was probably a thing at one time.

        C++ is a perfectly valid school of magic.

        JonBJ 1 Reply Last reply
        0
        • fcarneyF fcarney

          @JonB What is weird is the qDebug() << this and qDebug << ptr both print 0x0. So I can view that this is null, but when it was making decisions based upon this and ptr it assumed it cannot be null and didn't branch. Somehow the function call sidesteps this. Yeah, I have not tried a higher level of optimization.

          I think in the early C++ days the compilers were a lot less smart. So the scenario you describe was probably a thing at one time.

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote on last edited by JonB
          #4

          @fcarney
          The thing about compilers optimizing away this == nullptr was mentioned several times in SO posts, so obviously a known optimization. I don't know what level of optimization/which compilers they were on though.

          1 Reply Last reply
          0
          • fcarneyF Offline
            fcarneyF Offline
            fcarney
            wrote on last edited by
            #5

            Huh, my project for testing this has -O3 set: QMAKE_CXXFLAGS += -O3
            I also tried in release and the function call wins out.

            C++ is a perfectly valid school of magic.

            JonBJ 1 Reply Last reply
            0
            • fcarneyF fcarney

              Huh, my project for testing this has -O3 set: QMAKE_CXXFLAGS += -O3
              I also tried in release and the function call wins out.

              JonBJ Offline
              JonBJ Offline
              JonB
              wrote on last edited by JonB
              #6

              @fcarney
              The forthcoming quantum version of gcc analyzes all possible paths in other universes and hence realizes this can't* [* 99.9999999999% certain] be null, and will optimize even your function call away....

              1 Reply Last reply
              1
              • S Offline
                S Offline
                SimonSchroeder
                wrote on last edited by
                #7

                I guess that calling a member method on a nullptr will not crash when you are not accessing any members (and the member function is not virtual). This is probably the reason why deleteLater() does not crash. This supposes, however, that QDeferredDeleteEvent() either checks for nullptr or just calls delete without accessing any members before.

                I guess you can just rewrite your checkNull to: static bool checkNull(void* vptr) { return vptr; }. This brings up the idea if if(checkNull(this)) ... can be replaced by if(static_cast<bool>(this)) ... to prevent the compiler from optimizing it away.

                Fun fact: Did you know that it is legal to have delete this; as the last statement in a member function?

                JonBJ 1 Reply Last reply
                0
                • J.HilkJ Offline
                  J.HilkJ Offline
                  J.Hilk
                  Moderators
                  wrote on last edited by J.Hilk
                  #8

                  @fcarney said in Calling methods on C++ classes that are null:

                  postEvent

                  the first thing postEvent does, is checking for nullptr, if it's null, it returns immediately.

                          void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
                  	{
                  	    Q_TRACE_SCOPE(QCoreApplication_postEvent, receiver, event, event->type());
                  	
                  	    if (receiver == 0) {
                  	        qWarning("QCoreApplication::postEvent: Unexpected null receiver");
                  	        delete event;
                  	        return;
                  	    }
                  

                  ok I now get, what you're actually asking :D

                  I would actually say, that the compiler properly inlines this

                  void QObject::deleteLater()
                  {
                      QCoreApplication::postEvent(this, new QDeferredDeleteEvent());
                  }
                  
                  

                  and therefore it's a pseudo static function call ? :D 🤷‍♂️


                  Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


                  Q: What's that?
                  A: It's blue light.
                  Q: What does it do?
                  A: It turns blue.

                  1 Reply Last reply
                  0
                  • S SimonSchroeder

                    I guess that calling a member method on a nullptr will not crash when you are not accessing any members (and the member function is not virtual). This is probably the reason why deleteLater() does not crash. This supposes, however, that QDeferredDeleteEvent() either checks for nullptr or just calls delete without accessing any members before.

                    I guess you can just rewrite your checkNull to: static bool checkNull(void* vptr) { return vptr; }. This brings up the idea if if(checkNull(this)) ... can be replaced by if(static_cast<bool>(this)) ... to prevent the compiler from optimizing it away.

                    Fun fact: Did you know that it is legal to have delete this; as the last statement in a member function?

                    JonBJ Offline
                    JonBJ Offline
                    JonB
                    wrote on last edited by JonB
                    #9

                    @SimonSchroeder said in Calling methods on C++ classes that are null:

                    I guess that calling a member method on a nullptr will not crash

                    Maybe, maybe not, but it is "undefined behaviour" according to C++ standard.

                    if(checkNull(this)) ... can be replaced by if(static_cast<bool>(this)) ... to prevent the compiler from optimizing it away.

                    How so? Since compiler knows/assumes this != nullptr it should know static_cast<bool>(this) == true, so why not same optimization as already observed?

                    1 Reply Last reply
                    0

                    • Login

                    • Login or register to search.
                    • First post
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • Users
                    • Groups
                    • Search
                    • Get Qt Extensions
                    • Unsolved