Non virtual and inheritance

Discussion in 'C++' started by Sensei, Oct 18, 2012.

  1. Sensei

    Sensei Guest

    I was philosophizing on inheritance in C++, and I was wondering this.
    Bear with me, as this is something more of a philosophy/history, rather
    than a technical one.

    What would be a use case for non virtual members in a class hierarchy?


    class Base
    {
    public:
    virtual void hello() { std::cout << "Base" << std::endl; }:
    }

    class Derived : public Base
    {
    public:
    virtual void hello() { std::cout << "Derived" << std::endl; }:
    }


    Virtual allows me to use a Base class, and yet call the right function
    hello().


    Base *b = new Base;
    Base *d = new Derived;
    b->hello();
    d->hello();


    Now, if I let virtual out from Base and Derived, I will "slice" the
    derived class, and d->hello() will print, "Base".

    Why is a member function non-virtual by default, even from an
    historically accurate point of view?

    From a user point of view, the "slicing" is an "error", or an
    unexpected behavior, to say the least. Having a base class pointer is
    useful, and it will call the right derived class when needed.

    Not only that, but choosing non-virtual as the default behavior, means
    that virtual is quite "extravagant", and therefore needs to be added by
    hand. But, at least in my experience, virtual is a given, and
    non-virtual is the exception.

    I'd really like to understand the rationale behind this choice, with a
    use case that makes sense from an operational point of view, and not
    just as an academic exercise.


    This question bugged me when I tried to make only Derived non-virtual,
    and subclassing it.


    class Base
    {
    public:
    virtual void hello() { std::cout << "Base" << std::endl; };
    };

    class Derived : public Base
    {
    public:
    void hello() { std::cout << "Derived" << std::endl; };
    };

    class Last : public Derived
    {
    public:
    void hello() { std::cout << "Last" << std::endl; };
    };


    At least on my system (Apple clang version 4.1 x86_64-apple-darwin12.2.0
    Thread model: posix), it seems that "virtual" survives:


    Base *b = new Base;
    Base *d = new Derived;
    Base *l = new Last;
    b->hello();
    d->hello();
    l->hello();

    % clang++ a.cpp && ./a.out
    clang++ a.cpp && ./a.out
    Base
    Derived
    Last


    Is this the correct behavior? Why is this?


    Thanks!!
    Sensei, Oct 18, 2012
    #1
    1. Advertising

  2. Sensei

    Nobody Guest

    On Thu, 18 Oct 2012 13:55:35 +0200, Sensei wrote:

    > Why is a member function non-virtual by default, even from an historically
    > accurate point of view?


    Virtual calls are more computationally expensive, and the presence of any
    virtual methods make the class a non-POD type. Virtual calls are pointless
    if the class isn't intended to be derived from (as most STL containers
    aren't).
    Nobody, Oct 18, 2012
    #2
    1. Advertising

  3. Sensei

    Rui Maciel Guest

    Sensei wrote:

    > I was philosophizing on inheritance in C++, and I was wondering this.
    > Bear with me, as this is something more of a philosophy/history, rather
    > than a technical one.
    >
    > What would be a use case for non virtual members in a class hierarchy?
    >
    >
    > class Base
    > {
    > public:
    > virtual void hello() { std::cout << "Base" << std::endl; }:
    > }
    >
    > class Derived : public Base
    > {
    > public:
    > virtual void hello() { std::cout << "Derived" << std::endl; }:
    > }
    >
    >
    > Virtual allows me to use a Base class, and yet call the right function
    > hello().
    >
    >
    > Base *b = new Base;
    > Base *d = new Derived;
    > b->hello();
    > d->hello();
    >
    >
    > Now, if I let virtual out from Base and Derived, I will "slice" the
    > derived class, and d->hello() will print, "Base".
    >
    > Why is a member function non-virtual by default, even from an
    > historically accurate point of view?


    See the answer provided by Nobody.


    > From a user point of view, the "slicing" is an "error", or an
    > unexpected behavior, to say the least. Having a base class pointer is
    > useful, and it will call the right derived class when needed.


    No. It may go against your perception of how virtual functions work, but it
    is far from an error. If you define void Base::hello() and when you call
    the member function hello() of an object of type Base , you cannot be
    surprized if, by doing so, you end up calling void Base::hello(). The
    program is running exactly as you set it to run.


    > Not only that, but choosing non-virtual as the default behavior, means
    > that virtual is quite "extravagant", and therefore needs to be added by
    > hand. But, at least in my experience, virtual is a given, and
    > non-virtual is the exception.


    Then declare the member functions of your base class as virtual. If you
    declare a virtual member function in a base class then whenever you define
    the same member function in a derived class, that member function will be
    virtual whether you explicitly define it as virtual or not.


    > I'd really like to understand the rationale behind this choice, with a
    > use case that makes sense from an operational point of view, and not
    > just as an academic exercise.


    <source lang="cpp">
    #include <iostream>


    struct Base
    {
    void foo() { }
    };

    struct DerivedA: public Base
    {
    virtual void foo() { }
    };

    struct DerivedB: public Base
    {
    void foo() { }
    };



    int main(void)
    {
    using namespace std;
    cout << "Base size: " << sizeof(Base) << "\n";
    cout << "DerivedA size: " << sizeof(DerivedA) << "\n";
    cout << "DerivedB size: " << sizeof(DerivedB) << endl;
    return 0;
    }
    </source>

    <shell>
    rui@kubuntu:tmp$ g++ main.c++
    rui@kubuntu:tmp$ ./a.out
    Base size: 1
    DerivedA size: 8
    DerivedB size: 1
    </shell>


    > This question bugged me when I tried to make only Derived non-virtual,
    > and subclassing it.
    >
    >
    > class Base
    > {
    > public:
    > virtual void hello() { std::cout << "Base" << std::endl; };
    > };
    >
    > class Derived : public Base
    > {
    > public:
    > void hello() { std::cout << "Derived" << std::endl; };
    > };
    >
    > class Last : public Derived
    > {
    > public:
    > void hello() { std::cout << "Last" << std::endl; };
    > };
    >
    >
    > At least on my system (Apple clang version 4.1 x86_64-apple-darwin12.2.0
    > Thread model: posix), it seems that "virtual" survives:
    >
    >
    > Base *b = new Base;
    > Base *d = new Derived;
    > Base *l = new Last;
    > b->hello();
    > d->hello();
    > l->hello();
    >
    > % clang++ a.cpp && ./a.out
    > clang++ a.cpp && ./a.out
    > Base
    > Derived
    > Last
    >
    >
    > Is this the correct behavior? Why is this?


    Yes. You've declared Base::hello() as virtual. Hence, every* time you
    declare that member function in a class that directly or indirectly derives
    from Base, the member function defined in the derived class will also be
    virtual.


    Rui Maciel


    * The "every time" bit may not be entirely correct in C++11, with the
    introduction of "final" and "override". Someone please chime in to clarify
    this bit.
    Rui Maciel, Oct 18, 2012
    #3
  4. Sensei

    Luca Risolia Guest

    On 18/10/2012 13:55, Sensei wrote:
    > I was philosophizing on inheritance in C++, and I was wondering this.
    > Bear with me, as this is something more of a philosophy/history, rather
    > than a technical one.


    Note that C++11 introduces virtual specifiers: new, final,explicit,
    override to make the kind of inheritance more clear.
    Luca Risolia, Oct 18, 2012
    #4
  5. Nobody <> wrote:
    > Virtual calls are more computationally expensive


    Actually if you actually measure the speed difference between calling
    a regular (non-inlined) function and a virtual function, the difference
    is barely measurable, if not inexistent. Yes, there's an additional
    indirection step, but its impact on the overall speed of the function
    call is quite minimal (especially if the function takes parameters and
    does something that takes many clock cycles).

    In short: If you are avoiding virtual functions because you fear that
    your program will become slower, then you are worrying for nothing.

    The actual problem with virtual functions (which is only a problem in
    certain cases) is that a class having virtual functions will be larger
    (typically by a pointer) than a class with no virtual functions. In
    most cases this doesn't matter, but if you are super-tightly optimizing
    the memory usage of a very small class, that size increase can be crucial.
    (If eg. your class only has eg. an integral as a member, making any of the
    functions virtual will double the size of the class.)

    (Virtuality may also impede or reduce the effectiveness of function
    inlining, but that's also something that is relevant only in certain
    cases.)

    This overhead is useless if you don't need virtual functions for anything.
    Juha Nieminen, Oct 19, 2012
    #5
  6. Sensei <> wrote:
    > Why is a member function non-virtual by default, even from an
    > historically accurate point of view?


    The design principle of C++ has always been "you don't pay for what
    you don't use."

    A class having virtual functions is larger than a class with no virtual
    functions, and in some cases that might matter. (Also, virtuality may
    impede or reduce the effectiveness of function inlining.)
    Juha Nieminen, Oct 19, 2012
    #6
  7. Sensei

    Nobody Guest

    On Fri, 19 Oct 2012 09:39:50 +0000, Juha Nieminen wrote:

    > Nobody <> wrote:
    >> Virtual calls are more computationally expensive

    >
    > Actually if you actually measure the speed difference between calling
    > a regular (non-inlined) function and a virtual function, the difference
    > is barely measurable, if not inexistent. Yes, there's an additional
    > indirection step, but its impact on the overall speed of the function
    > call is quite minimal (especially if the function takes parameters and
    > does something that takes many clock cycles).


    It wasn't always thus. CPUs were rather more naive when C++ was originally
    designed. But the biggest issue is that virtual typically inhibits
    inlining, which has an impact far beyond the cost of a function call.

    > In short: If you are avoiding virtual functions because you fear that
    > your program will become slower, then you are worrying for nothing.
    >
    > The actual problem with virtual functions (which is only a problem in
    > certain cases) is that a class having virtual functions will be larger
    > (typically by a pointer) than a class with no virtual functions.


    If the vptr is a problem, it isn't usually due to the size, but to making
    the type non-POD. E.g. being unable to pass such objects between processes
    using shared memory, or efficient I/O via mmap(), or making the memory
    layout conform to a pre-defined format.
    Nobody, Oct 19, 2012
    #7
  8. Nobody <> wrote:
    > If the vptr is a problem, it isn't usually due to the size, but to making
    > the type non-POD.


    It depends on the application, but it often is a question of size.
    If you need to optimize the size of a class (usually because you need
    to make millions of instantiations, and you need for them to be as fast
    as possible and take as little memory as possible), even one additional
    pointer may significantly increase memory usage.

    I'd say the size issue is (at least in my experience) significantly
    more prevalent than the non-POD issue.
    Juha Nieminen, Oct 19, 2012
    #8
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. qazmlp
    Replies:
    19
    Views:
    766
    Daniel T.
    Feb 4, 2004
  2. cppsks
    Replies:
    0
    Views:
    797
    cppsks
    Oct 27, 2004
  3. jlopes
    Replies:
    7
    Views:
    402
    jlopes
    Nov 19, 2004
  4. Ashwin
    Replies:
    2
    Views:
    332
    Pierre Barbier de Reuille
    Aug 1, 2006
  5. Replies:
    11
    Views:
    675
    James Kanze
    Sep 10, 2006
Loading...

Share This Page