在派生类中具有相同名称的成员变量

huangapple go评论69阅读模式
英文:

the member variable with the same name in derived class

问题

UPDATE: 行为与模板无关,因此

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

将产生相同的结果。因此,似乎Derived中的m_data和Base中的m_data都存在于内存布局中。如果我们在Derived中定义一个函数,例如Derived::print() { m_data->print() },这将使用Derived中的m_data,但是,如果在派生对象上调用基类函数,仍然会使用来自Base的m_data。

我对以下代码的行为感到惊讶,它打印出"from foo",而不是"from override foo"。为什么会这样?"m_data"不应该是"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;
}
英文:

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 &lt;iostream&gt;

using namespace std;

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

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

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

template &lt;class t&gt; 
struct Derived : public Base {
    t* m_data {new t()};
};

int main()
{
    Derived&lt;OverrideFoo&gt; d;
    d.useData();
    return 0;
}

答案1

得分: 1

当你调用d.useData()时,你正在调用Base::useData(),它访问的是Base::m_data

我猜你可能期望Base::useData()使用Derived::m_data,只是因为变量有相似的名称。然而,这不是它的工作方式。两个类都有自己独立的m_data,在你的情况下,它们的类型不同。

的确,Derived::m_data 隐藏了 Base::m_data,这可能会让你认为它们相关或者一个"覆盖"另一个。不要把它与隐藏混淆在一起。隐藏是类似命名的自然结果。如果Derived需要访问Base::m_data,它必须使用限定符以消除与自己的m_data的歧义。

注意:成员变量/字段不能被覆盖。如果你需要覆盖式的行为,你需要通过成员函数来实现(类似于virtual IPrintable* GetPrintable())。并且基类必须使用virtual关键字来授予覆盖的可能性。

另一种思考的方式是:Base,尽管其名称可能暗示了什么,是一个完整的类型。你可以使用Base x;来实例化并使用这个类,而不需要派生它。编译器会为Base生成完整且功能完备的代码,包括访问Base::m_data的代码。如果m_data以某种方式是可覆盖的,那么这个代码怎么生成呢?如果它的数据类型可以在某个基类中被覆盖,那么Base会理解sizeof(*m_data)是什么呢?如果你建议它可以被任何派生类更改,编译器怎么知道m_data指的是什么?

另一个要点是:如果成员默认可以被覆盖(不需要virtual关键字),那么会对基类造成大量混乱。想象一下编写一个通用的基类,可能派生类不知不觉地改变基类的状态?或者想象一下编写一个派生类,担心你的变量命名,因为"也许一个基类使用了相同的名称?"

所以,让我们总结一下关键要点:

  1. 字段不能被覆盖,无论如何。这将破坏sizeof()等许多东西(完全不同的话题)。
  2. 基类必须明确使用virtual关键字来允许派生类覆盖成员函数。

不过,可能有更好的方法来实现你的目标。对我来说,最自然的方法是将Foo类型指定为Base的模板参数。

像这样:

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;
}

很难知道对你来说最好的方法是什么,因为这是一个不切实际的编造的例子。

英文:

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(&quot;from foo\n&quot;);
    }
};

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

template&lt;typename TData&gt;
struct Base {
  void useData() {
      m_data.print();
  }
  TData m_data;
};

template &lt;typename TData&gt; 
struct Derived : public Base&lt;TData&gt; {
};

int main()
{
    Derived&lt;Foo1&gt; d1;
    d1.useData();
    Derived&lt;Foo2&gt; d2;
    d2.useData();
    return 0;
}

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

答案2

得分: 0

尝试运行这段代码,你会发现这两个 m_data 具有不同的内存地址,这意味着它们是不同的变量。

#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;
}
英文:

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

#include &lt;iostream&gt;

using namespace std;

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

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

struct Base {
  void useData() {
      m_data-&gt;print();
      std::cout &lt;&lt; m_data &lt;&lt; std::endl;
  }
  Foo* m_data {new Foo()};  
};

template &lt;class t&gt; 
struct Derived : public Base {
    t* m_data {new t()};
};

int main()
{
    Derived&lt;OverrideFoo&gt; d;
    d.useData();
    d.m_data-&gt;print();
    std::cout &lt;&lt; d.m_data &lt;&lt; std::endl;
    return 0;
}

huangapple
  • 本文由 发表于 2023年1月9日 17:38:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/75055395.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定