C++ Inheritance: Derived class pointer to a Base class invokes Derived class method

前端 未结 6 1783
眼角桃花
眼角桃花 2021-01-25 20:28

I am learning C++ inheritance, so I tried this code by creating a Base class dynamically and made a downcast to its Derived class (obviously its not valid to downcast) in order

相关标签:
6条回答
  • 2021-01-25 20:40

    As @Mark Ransom has suggested in the comments, I looked the post When does invoking a member function on a null instance result in undefined behavior?. Then I came up with my own solution.

    This behavior of invoking a non created object's method is not a matter of fact with the parent class nor child class or downcast or inheritance . I had statically told to call the child::who() by downcasting, the compiler calls child::who() without caring about the object type.

    But how the who() method is called since no object is created for child class?

    The method which I tried to invoke doesn't have any member variable so there is no need to dereference this pointer. It just calls who(), the same behavior is true if the child class pointer points to NULL also.

    child *c = NULL;
    c->who(); 
    

    It prints I am child even though c points to NULL. Because no need to dereference this pointer. Lets have a situation where who() method contains a member variable x as below.

    class child : public parent{
        private:
            int x;
        public:
            void who(){
                x = 10;
                cout << "I am child " << x;
            }
    };
    
    child *c = NULL;
    c->who()
    

    Now it causes Segmentation fault because in order to use x the compiler have to dereference this pointer for x as (*this).x. Since this points to NULL dereferencing will cause Segmentation fault. This is clearly an undefined behavior.

    0 讨论(0)
  • 2021-01-25 20:45

    Quite simply, at the point that you invoke c->who(), the static type of c (i.e. the type the compiler knows for c) is child* and the method who is non virtual. So the compiler emits the instructions for a call to the address of child::who.

    The compiler does not (and, in general, how could it?) keep track of the true type of the object that would be pointed to by c at runtime.

    If you had any members in child that are not in parent, accessing those members in child::who would have produced an out-of-bounds access, which might cause a SIGSEGV, or other unpleasantness.

    Finally, as to whether your observed behavior is guaranteed by the standard, I tend to agree with @P45Imminent: Both parent and child satisfy the requirements for POD and child has no non-static data members. Consequently the runtime layout of parent and child objects is required to be indistinguishable per the standard (at least as far as methods of parent and child are concerned -- perhaps child and parent could have different amounts of padding at the end?). So the line from 9.3.1/2 quoted in one of the comments does not apply IMHO. I'd love to hear from folks more knowledgeable if this assumption on layout is not supported by the standard.

    0 讨论(0)
  • 2021-01-25 20:54

    The reason your code behaves this way is probably because the compiler doesn't check the actual type of your object (it isn't required to unless your function is virtual); it just calls child::who because you told it to. That said, your code is certainly suspect.

    You are statically downcasting the base class pointer to a derived class pointer, which not type-safe. C++ will not prevent you from doing this; it is up to you to ensure that your pointer really does point to an object of the derived type. Dereferencing the pointer if it does not refer to an object of the derived type is potentially undefined behaviour. You are lucky your code even prints anything at all.

    You need to make sure your base class function who is virtual, otherwise the function call will not behave polymorphically. Keep in mind, once you add virtual to your code, you will certainly be invoking undefined behaviour, because you are illegally downcasting to an invalid type. You can safely downcast using dynamic_cast, which will return a nullptr if the object is not of the specified type.

    You should also generally have a virtual destructor in your base class, so that your object can be deleted polymorphically. Speaking of which, you also need to make sure to delete your dynamically allocated object at some point. By manually calling new and delete, it is very easy to leak memory like this, even if you know that you need to call delete. std::unique_ptr and std::shared_ptr were added to the standard library in C++11 to address these concerns. Use these instead of new and delete in all but the most low-level code.

    To summarize, here is how I suggest your code should look:

    #include <iostream>
    #include <memory>
    
    class parent {
        public:
            virtual ~parent() {}
    
            virtual void who() {
                std::cout << "I am parent";
            }
    };
    
    class child : public parent {
        public:
            void who() override {
                std::cout << "I am child";
            }
    };
    
    int main() {
        auto p = std::make_unique<parent>();
        auto c = dynamic_cast<child*>(p.get());
    
        if (c) // will obviously test false in this case
        {
            c->who();
        }
    }
    
    0 讨论(0)
  • 2021-01-25 20:55

    What you are seeing is Undefined Behavior at work.

    Your functions are non-virtual, they are simply member functions of the type you've told the compiler the pointer points to.

    child *c = (child*)new parent;
    

    This is a c-style cast that strong-arms the compiler into the belief that the pointer c definitely points to something that is a child.

    Thus, when you call c->who(), you are specifically calling child::who, because the pointer is a pointer-to-child.

    The reason nothing terrible happens and you see "I am child" is because you don't try to dereference that pointer or make use of any of the child-specific fields that your pointed-to-instance doesn't actually have. So you get away with it.

    0 讨论(0)
  • 2021-01-25 21:05

    Polymorphism doesn't work like that. Interestingly your C-style cast from parent* to child* works because the classes don't have a v-table or anything else other than the function who. So the address of who must be the same as the address of the class itself.

    parent *p = (parent*)new child();

    will make more sense, but even then, p->who() would only call the child class function if you mark the function who virtual in the parent class, which you haven't done.

    0 讨论(0)
  • 2021-01-25 21:06

    first of all, this is no valid downcast. The real type of new parent() indeed is parent and not child. Downcasting is only allowed if the real (also called dynamic) type is child but the pointing object is a parent at the moment.

    The other way around would make more sense. If you create a child and assign it to a parent pointer, this would be fine. But even then: Unless who is virtual the static type instead of the dynamic type decides which function is called.

    Example:

    class parent{
        public:
            void who(){
                cout << "I am parent";
            }
    
        ~virtual parent() {}; // important to have that in class hierarchies where you might use a child that is assigned to a parent pointer!
    };
    
    class child : public parent{
        public:
            void who(){
                cout << "I am child";
            }
    };
    
    int main(){
        parent *c = (parent *)new child();
        c->who(); // output would still be parent because who() is not virtual
        delete c; // important! never forget delete!
        return 0;
    }
    

    If you use, on the other hand,

    virtual void who();
    

    then, the output would be "I am child" like you would expect.

    0 讨论(0)
提交回复
热议问题