Thread: Some syntactical issues - C++ inheritance

  1. #1
    Registered User
    Join Date
    Nov 2019
    Posts
    135

    Some syntactical issues - C++ inheritance

    Hello everyone.

    I have some project to submit which involves inheritance, abstract classes etc... So I went through the related PPT - highlighted some points I don't understand.


    1. First issue - Highlighted in the code:


    Code:
    class A { 
    public:
       int y;
    };
    
    class B: private A { 
    public:
       int x;
       void f() {
          cout << x << “,” 
            << y << endl;
       }
    };
    
    int main ()
    {
       A a;
       a.y = 5;
       B b;
       b.x = 2;
       b.f();
       A b_as_a = b; // What's the problem with this line?
    }
    2. Another similar question:

    Code:
    class A { 
    public:
       int x;
    };
    
    class B: private A { 
    public:
       int x;
       void f() {
          cout << x << “,” 
            << A::x << endl;
       }
    };
    
    int main ()
    {
       A a;
       a.x = 5; // OK
       B b;
       b.x = 2; // OK
       b.A::x = 5; // ERROR
       b.f();
    }
    I don't fully understand which role x plays here actually. The derived class, B, has two x's or only one?
    If it has two of x's: which one of them is accessible and how?
    If it has one x: which one? Derived from the base class or the public one of the deriving class?

    It's peculiar, b.x is accessible where b.A::x isn't. They are the same x - no?


    3. Another question regarding the order of initialization list when inheritance is used:

    The order of member initializers in the initialization list is irrelevant! [/CODE]
    [CODE]The actual order of initialization is as follows (from the C++ standard):
    1.If the constructor is for the most-derived class, virtual base classes are initialized in the order in which they appear in depth-first left-to-right traversal of the base class declarations (left-to-right refers to the appearance in base-specifier lists)
    2.Then, direct base classes are initialized in left-to-right order as they appear in this class's base-specifier list
    3.Then, non-static data members are initialized in order of declaration in the class definition
    4.Finally, the body of the constructor is executed

    Can someone explain the highlighted sections? What do they mean by virtual base classes and direct base classes?

    4. Another thing - virtual destructors:

    Code:
    class Base
    { 
    public:
       virtual ~Base();
    }; 
    class Derived : public Base
    { 
    public:
       virtual ~Derived();
    };
    Base *p = new Derived; 
    delete p;
    Which destructor is called? (Derived::~Derived())

    But when we instantiate a Derived object - we also instantiate a Base object - but we destruct only the derived object.
    How do we know that the Base object's destructor is also invoked?


    5. Pure virtual classes - abstract classes:
    Code:
    class Shape
    { 
    public:
       virtual ~Shape(); //Virtual destructor
       virtual void Draw() = 0; //Pure virtual
       virtual double Area() = 0; //Also 
    };
    Why don't we compare also the default constructor to 0? We can't instantiate objects of type Shape - so this destructor is never invoked.

    6. Regarding final:
    Code:
    class Base
    {
       // final identifier marks this function as
       // non-overrideable
       virtual void A() final;
    };
     
    class Derived: public Base
    {
       // trying to override final function
       // Base::A() will cause a compiler error
       virtual void A();
    };
    If we don't want a function to be overridable - why do we initially declare it as a virtual one?

  2. #2
    Registered User
    Join Date
    Dec 2017
    Posts
    1,626
    0. You should read over your question after you post it and fix the errors. Surely you didn't mean to have all of that bolding; maybe the section titles, but everything? There are also some extraneous CODE tags (in question 3), which makes me wonder if something is missing.

    1. The base class is derived from as private, so what was public (or protected) in A is private in B. Change private A to public A and it will work.

    2. The x's are two separate variables. The only reason you can't access b.A::x in your example is the same as for question 1.

    3. You should post a link to where you got this stuff from so we can check its provenance.

    a. The order of member initializers in the initialization list is irrelevant!
    This is saying that the order that objects are actually initialized is determined by how they are ordered in the class definitions, not by the order in the initialization list. Compilers often warn if you don't have your initialization list in the same order as things are actually initialized, just in case you expect something different.

    b. Virtual base classes: In C++, what is a virtual base class? - Stack Overflow
    Direct base classes are just not virtual.

    4. Note that although a Derived is constructed (along with it's Base, of course) it's stored in a Base pointer, which calls ~Base. However, since ~Base is virtual it is able to call ~Derived first. Add some couts to see the order of execution.

    5. It wouldn't make sense for ~Shape to be pure virtual, which would imply that the Derived class must supply it. But how could it do so? Shape could have some private data that it needs to clean up. Also, when deleting the object from a pointer to Shape, Shape's virtual dtor allows it to invoke the Derived object's dtor.

    6. final doesn't make sense in that situation. Usually it would be done in a Derived class so that the virtual function cannot be further overridden.
    Code:
    class B {
        virtual void f();
    };
    class D1 : public B {
        void f() override final;
    };
    class D2 : public D1 {
        void f() override;   // no-go
    };
    Last edited by john.c; 01-01-2020 at 02:21 PM.
    A little inaccuracy saves tons of explanation. - H.H. Munro

  3. #3
    Registered User
    Join Date
    Nov 2019
    Posts
    135
    @john.c -

    0. You're right, it's messy and it won't happen again.

    1. Ah, so we can't assign b's y (hidden) to b_as_a's y.

    2. Got it. But how can an object maintain two members with the same name?

    3. These examples are taken from the course material. Thank you!

    4-5. Got it.

    6. Thank you!

  4. #4
    Registered User
    Join Date
    Dec 2017
    Posts
    1,626
    It's only a little messy. I always preview my posts. Sometimes the site adds extra blank lines, double-spacing things, which is annoying.

    2. Usually if the member of the base is meant to be manipulated in the derived class you would ensure that you aren't "shadowing" names when writing the derived class. However, even if the names are the same, they are distinguishable in the way the question shows.

    Also, we still need laserlight to tell us what I got wrong! For instance, maybe there is some case where final is reasonable for a virtual function in the ultimate base class. I can't see why, though.
    A little inaccuracy saves tons of explanation. - H.H. Munro

  5. #5
    Registered User
    Join Date
    Nov 2019
    Posts
    135
    @jogn.c - Thank you.

  6. #6
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by HelpMeC
    But when we instantiate a Derived object - we also instantiate a Base object - but we destruct only the derived object.
    How do we know that the Base object's destructor is also invoked?
    The destructor for a derived class always invokes the destructors for its base class(es) at the end in order to destroy the base class subobject(s). This is true whether or not the destructors are declared virtual; the use of virtual is so that the correct derived class destructor is invoked despite the destruction happening via a pointer to a base class.

    Quote Originally Posted by HelpMeC
    Why don't we compare also the default constructor to 0? We can't instantiate objects of type Shape - so this destructor is never invoked.
    A C++ class that has at least one pure virtual member function is an abstract base class, not a pure interface construct. While both abstract base classes and pure interface constructs cannot be instantiated, the difference is that abstract base classes can have member variables, which means that they could have constructors to construct these member variables. Such constructors could be invoked by the constructors of derived classes. If they don't have any member variables, then you don't need any constructors (other than what is provided implicitly by the compiler), and the abstract base class is effectively a pure interface, but it is not necessarily the case.

    Likewise, this means that an abstract base class must have a destructor. It is true that sometimes an abstract base class does not have any good candidate for a pure virtual member function, in which case the destructor would be declared pure virtual in order to meet the requirements to make the class abstract, but even in such a case the destructor must still be defined so that the destructors of derived classes can (implicitly) invoke it.

    Quote Originally Posted by HelpMeC
    If we don't want a function to be overridable - why do we initially declare it as a virtual one?
    You might be able to use this to prevent a derived class implementer from accidentally overloading when they thought they could override, but the right approach to prevent that is to always use the override keyword when overriding: if the base class member function is not intended to be overriden (or you made a mistake with the parameter list, or even just made a typo concerning the function name), the compiler will then report an error. Furthermore, the intended non-virtual function call will then always really be non-virtual, whereas with a virtual function declared final the compiler might (should?) be able to optimise away the virtual call when it is invoked through a base class pointer/reference, but it is implementation detail so it might not be required.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  7. #7
    Registered User
    Join Date
    Nov 2019
    Posts
    135
    @laserlight -
    Understood your points except for two little ones:
    1. "but even in such a case the destructor must still be defined so that the destructors of derived classes can (implicitly) invoke it."
    If we declare the base class's destructor pure virtual - it couldn't be defined anyway. In this case we enforce the derived classes to define their cont' and that's it.

    2. "
    Furthermore, the intended non-virtual function call will then always really be non-virtual, whereas with a virtual function declared final the compiler might (should?) be able to optimise away the virtual call when it is invoked through a base class pointer/reference, but it is implementation detail so it might not be required."
    Didn't succeed to figure this case out. Can you explain it more in-depth? If it's not necessary - we'll leave it.

    Thank you!

    BTW, to my understanding, as long as we have some variables declared in the base class - we don't ever want to define its destructor as a pure virtual one.
    Last edited by HelpMeC; 01-03-2020 at 07:04 AM.

  8. #8
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by HelpMeC
    If we declare the base class's destructor pure virtual - it couldn't be defined anyway. In this case we enforce the derived classes to define their cont' and that's it.
    Try to compile this program:
    Code:
    #include <iostream>
    
    class A
    {
    public:
        virtual ~A() = 0;
    };
    
    class B : public A
    {
    public:
        virtual ~B()
        {
            std::cout << "B::~B()" << std::endl;
        }
    };
    
    int main()
    {
        B b;
    }
    Then compile this program:
    Code:
    #include <iostream>
    
    class A
    {
    public:
        virtual ~A() = 0;
    };
    
    class B : public A
    {
    public:
        virtual ~B()
        {
            std::cout << "B::~B()" << std::endl;
        }
    };
    
    A::~A()
    {
        std::cout << "A::~A()" << std::endl;
    }
    
    int main()
    {
        B b;
    }
    Quote Originally Posted by HelpMeC
    Didn't succeed to figure this case out. Can you explain it more in-depth? If it's not necessary - we'll leave it.
    Virtual calls (i.e., those that involve the virtual function call mechanism, which typically is implemented as pointers to a virtual table) incur a small cost compared to non-virtual calls, often negligible compared to the work the function actually does, but the cost is still there (and furthermore the difference could be greater if the non-virtual call is inlined). An invocation of a virtual function doesn't have to be a virtual call: if the compiler can determine that a non-virtual call will do (e.g., you're calling the overriden virtual function... but directly with a derived class object rather than via a pointer/reference to the base class), it will almost certainly skip making it a virtual call.

    Quote Originally Posted by HelpMeC
    BTW, to my understanding, as long as we have some variables declared in the base class - we don't ever want to define its destructor as a pure virtual one.
    That's not true. You only want to declare the destructor pure virtual when the class should be an abstract base class, but there are no good candidate virtual functions to declare as pure virtual.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  9. #9
    Registered User
    Join Date
    Nov 2019
    Posts
    135
    @laserlight -
    Thanks for the amazing clarifications and "good to knows" details!

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Some syntactical question on C language
    By HelpMeC in forum C Programming
    Replies: 6
    Last Post: 11-30-2019, 04:23 PM
  2. Inheritance
    By gr8prog in forum C# Programming
    Replies: 2
    Last Post: 03-06-2014, 06:12 PM
  3. Class inheritance and method issues
    By Khabz in forum C++ Programming
    Replies: 1
    Last Post: 11-11-2013, 11:40 AM
  4. Replies: 5
    Last Post: 04-15-2009, 02:47 PM
  5. inheritance
    By Hunter2 in forum C++ Programming
    Replies: 4
    Last Post: 07-08-2002, 12:10 PM

Tags for this Thread