Home > Back-end >  the member variable with the same name in derived class
the member variable with the same name in derived class

Time:01-09

UPDATE: the behaviour is not template specific, so

struct Derived : public Base {
    OverrideFoo* m_data {new OverrideFoo()};
}

will do the same. so it seems m_data in Derived and m_data in Base both exist in memory layout. If we define a function in Derived, e.g., Derived::print() { m_data->print()}, this will use m_data in Derived, however, if base function is called on derived object, it still use m_data from Base.

I was surprised with the behaviour of the following code, it prints "from foo", rather than the "from override foo". why is it like this? shouldn't the "m_data" be the type of "OverrideFoo"?

#include <iostream>

using namespace std;

struct Foo {
    void print() {
        printf("from foo\n");
    }
};

struct OverrideFoo {
    void print() {
        printf("from override foo\n");   
    }
};

struct Base {
  void useData() {
      m_data->print();
  }
  Foo* m_data {new Foo()};  
};

template <class t> 
struct Derived : public Base {
    t* m_data {new t()};
};

int main()
{
    Derived<OverrideFoo> d;
    d.useData();
    return 0;
}

CodePudding user response:

When you call d.useData(), you are calling Base::useData(), which accesses Base::m_data.

I suppose you're expecting Base::useData() to use Derived::m_data, just because the variable has a similar name. However that's not how this works. Both classes get their own independent m_data, and in your case, with different types.

It's true that Derived::m_data hides Base::m_data, which may suggest to you that they are related or that one "overrides" the other. Don't confuse that with hiding. The hiding is a natural consequence of the similar naming. If Derived needs to access Base::m_data, it must qualify it in order to disambiguate from its own m_data.

Note: Member variables / fields cannot be overridden. If you need an override-style behavior, you'll need to do it via a member function (something like virtual IPrintable* GetPrintable(). And the base class must grant the possibility of overriding with the virtual keyword.

Another way to think about this: Base, despite what its name suggests, is a complete type. You can do Base x; to instantiate and use this class, without being derived. The compiler generates code for Base which is complete and functional, including the code to access Base::m_data. If m_data were somehow overrideable, how could this code be generated? What would Base understand sizeof(*m_data) to be, if its datatype could be overridden in some base class? How would the compiler know what m_data even refers to, if you're suggesting it can be changed by any class which derives it?

Another point: If members were able to be overridden by default (without the virtual keyword), it would cause mass chaos for base classes. Imagine writing a generic base class and risking that derived classes could unknowingly change the state of the base? Or imagine writing a derived class and being concerned about your variable naming because "well maybe a base class used the same name?"

So let's summarize the key points:

  1. Fields cannot be overridden, period. It would break sizeof() among lots of other things (whole other topic)
  2. Base classes must explicitly grant derived classes to override member functions via the virtual keyword.

There are probably better ways to do what you're attempting though. The most natural for me would be to specify the Foo type as a template parameter to Base.

Like this:

struct Foo1 {
    void print() {
        printf("from foo\n");
    }
};

struct Foo2 {
    void print() {
        printf("from override foo\n");   
    }
};

template<typename TData>
struct Base {
  void useData() {
      m_data.print();
  }
  TData m_data;
};

template <typename TData> 
struct Derived : public Base<TData> {
};

int main()
{
    Derived<Foo1> d1;
    d1.useData();
    Derived<Foo2> d2;
    d2.useData();
    return 0;
}

It's hard to know the best approach for you, because this is an unrealistic contrived example.

CodePudding user response:

Try this code out and you will find that the two m_data has different memory address, which means they are different variable.

#include <iostream>

using namespace std;

struct Foo {
    void print() {
        printf("from foo\n");
    }
};

struct OverrideFoo {
    void print() {
        printf("from override foo\n");   
    }
};

struct Base {
  void useData() {
      m_data->print();
      std::cout << m_data << std::endl;
  }
  Foo* m_data {new Foo()};  
};

template <class t> 
struct Derived : public Base {
    t* m_data {new t()};
};

int main()
{
    Derived<OverrideFoo> d;
    d.useData();
    d.m_data->print();
    std::cout << d.m_data << std::endl;
    return 0;
}
  • Related