【问题标题】:Few Basic Questions in Overriding覆盖中的几个基本问​​题
【发布时间】:2010-05-26 03:02:48
【问题描述】:

我的基本问题很少,如果有人能解决这个问题,我将不胜感激。

1:我说base *b是什么意思 = 新派生;为什么要这样做?我们很好分开可以 为类库创建对象和 类派生,然后调用 相应地发挥作用。我知道 此基础 *b = 新派生;是 称为对象切片,但为什么和 什么时候会这样做?

2:我知道 为什么不建议转换 要派生的基类对象 类对象(因为基类是 不知道派生类 成员和方法)。我什至读了 其他 StackOverflow 线程,如果 会是这样,然后我们 必须更改/重新访问我们的设计。 我明白这一切,然而,我是 只是好奇,有什么办法吗 这个?

    class base
    {
    public:
        void f(){cout << "In Base";}
    };

    class derived:public base
    {
    public:
        void f(){cout << "In Derived";}
    };

    int _tmain(int argc, _TCHAR* argv[])
    {
        base b1, b2;
        derived d1, d2;
        b2 = d1;
        d2 = reinterpret_cast<derived*>(b1); //gives error C2440
        b1.f(); // Prints In Base
        d1.f(); // Prints In Derived
        b2.f(); // Prints In Base
        d1.base::f(); //Prints In Base
        d2.f();
        getch();
        return 0;
    }

3:在我上面的例子中,有什么方法可以使用派生类对象调用基类 f() 吗?我使用了 d1.base()::f() 我只想知道是否有任何方法不使用范围解析运算符?

非常感谢您花时间帮助我!

【问题讨论】:

  • 请重新格式化以隔离代码。单击语法指南的小问号。
  • BennyG 感谢您清理我的代码格式。我可以自己编辑我的问题/我没有看到任何编辑按钮。因为,从“我知道为什么不建议将基类对象转换为派生......”开始的语句是我的第二个问题,但它错误地附加到了第一个问题。
  • 大致解决了这个问题。不过,您应该会在问题下方看到一个编辑按钮。
  • base *b = 新派生;不是对象切片。当您按值分配时会发生对象切片,例如 base b= d;,其中 d 是派生的实例。

标签: c++ polymorphism overloading overriding


【解决方案1】:

1.这不是对象切片:

base *b = new derived;

这是将base 类型的指针分配给derived 的实例。

一个(非常常见的)例子就是回调。考虑一下:

class Observer
{
public:
    virtual void OnEvent() = 0;
};

class Subject
{
public:
    Subject() : observer(0) {}
    void SetObserver(Observer* o) { observer = o; }
    void FireEvent() { if(observer != 0) observer->OnEvent(); }
private:
    Observer* observer;
};

class MyObserver : public Observer
{
public:
    void OnEvent() { ::printf("Hi there!\n"); }
};

int main()
{
    Subject s;
    MyObserver o;
    s.SetObserver(&o);
    s.FireEvent();
    return 0;
}

这应该是预期的输出:

你好!

注意这里发生了什么。我们将一个指向MyObserver 实例的指针传递给SetObserver(),即使该函数只接受Observer 类型的指针。这是因为MyObserver(公开)派生自Observer。在这种情况下,我们说MyObserver is-an Observer

Observer 类型定义了一个纯虚函数=0 表示该函数是纯的;它必须由派生类实现)。 virtual 关键字告诉编译器调用该函数应该导致执行最多派生的函数。 MyObserver 中的 OnEvent() 函数是派生最多的,因此,即使我们在 Observer 类型的指针上调用 OnEvent(),也会调用该版本。

我们遇到了麻烦,因为在这段代码中 Subject 不必知道其观察者的确切类型 - 观察者只需从 Observer 派生,Subject 实例将调用OnEvent() 的最衍生类型的实现。这允许代码解耦 - Subject 不依赖于 MyObserverMyObserver 不依赖于 Subject

2.将指针从基类强制转换为派生类型本身并没有错。以下内容实际上是合法的并保证有效:

class Base {};
class Derived : public Base {};

int main()
{
    Derived d;
    Base* bp = &d;
    Derived* dp = static_cast<Derived*>(bp);
    return 0;
}

然而,这个 sn-p 中的 return 语句之前的行是未定义的:

class Base {};
class Derived1 : public Base {};
class Derived2 : public Base {};

int main()
{
    Derived1 d1;
    Base* bp = &d1;
    Derived2* d2p = static_cast<Derived2*>(bp); // WTF?!
    return 0;
}

d2p 指针的值是没有意义的,试图访问其中的任何东西肯定会导致崩溃,因为bp 指针实际上并不指向Derived2 实例,它指向的是@ 987654353@ 实例。编译器在编译时无法捕捉到这一点,因为Derived1Derived2 都继承自Base,所以转换成功编译。这是从基类型转换为派生类型的主要危险 - 直到运行时您才会知道转换是否真的返回有意义的结果。

当然,除非你使用dynamic_cast&lt;&gt;(),但是这种转换会导致运行时损失。 static_cast&lt;&gt;() 最多涉及指针运算。 reinterpret_cast&lt;&gt;() 强制指针采用不同的(可能不相关的)类型,而不执行任何指针运算。这使得reinterpret_cast&lt;&gt;() 成为更危险的演员阵容之一,应该只在必要时使用,尤其是在static_cast&lt;&gt;() 可以完成这项工作的情况下。

3.考虑以下几点:

class Base
{
public:
    void Foobar() { ::printf("In Base!\n"); }
};

class Derived : public Base
{
public:
    void Foobar() { ::printf("In Derived!\n"); }
};

int main()
{
    Derived d;
    Derived* dp = &d;
    Base* bp = dp;
    dp->Foobar();
    bp->Foobar();
    return 0;
}

如果Foobar() 函数不是虚拟的,那么你会得到这个输出:

In Derived!
In Base!

否则,如果Foobar() 函数是虚函数,那么您将得到以下输出:

In Derived!
In Derived!

为了保证对虚函数Foobar() 的调用通过基指针调用基实现,那么您必须使用范围解析运算符:

// Prints "In Base!", even if bp actually points
// to an instance of Derived overriding Foobar().
bp->Base::Foobar();

【讨论】:

  • =0 表示该函数是纯函数 - 它不存在” - 不完全是,它表示派生类必须实现它。它仍然可以由基础实现,即virtual void f() = 0 {} 很好。
  • 好电话。我已经编辑了那句话。对于那些想知道为什么要定义纯虚函数的人,请参阅gotw.ca/gotw/031.htm
  • 那个信息打了我一巴掌 :) 我一直认为纯虚拟不能有身体
  • 另外,纯虚析构函数必须有一个主体。
【解决方案2】:

是和不是。你可以走了 class derived: public base { public: void f() { base::f(); } };

不过,我不太确定是否在全球范围内。

【讨论】:

    【解决方案3】:

    问题 1:您为什么要这样做:您可能不会直接这样做。发生这种情况的情况是,例如,您有一个返回“某种基数*”的函数,而您并不关心哪种类型。

    问题2:在派生中,您需要使用范围解析运算符来指定您要使用基类的实现。

    注意:你还没有声明你的函数是虚拟的,所以你实际上不会得到你期望的行为。

    【讨论】:

      【解决方案4】:
      base *b = new derived
      

      创建指向派生类对象的基类指针。这允许您创建 is-a 关系。类派生是一个基础。详情请参考inhertance

      2. d2 = reinterpret_cast&lt;derived*&gt;(b1); 您正在尝试将基类对象转换为派生类对象。但是使用的强制转换操作是 .不正确。

      如果您打算调用基类方法,请使用基类指针。

      Base* ptr = &b1;
      ptr->f();
      

      我建议你参考FAQ Lite

      【讨论】:

        【解决方案5】:
        base *b = new derived;

        (1) 您有一个指向派生类对象的基类指针。这在实现多态性时使用,其中您有几个从公共基类继承的派生类。然后,您可以通过基类指针根据其类型调用正确的派生类的函数,前提是该函数是虚拟的。

        
        class base
        {
            public:
                virtual void f(){ cout &lt&lt "In Base" &lt&lt endl; }
        };
        
        class derived:public base
        {
            public:
                void f(){ cout &lt&lt "In Derived" &lt&lt endl; }
        };
        
        class derived2: public derived
        {
        public:
            void f() { cout &lt&lt "In Derived2" &lt&lt endl; }
        };
        
        int _tmain(int argc, _TCHAR* argv[])
        {
            derived d1;
            derived2 d2;
            base* b;
            b = &d1;
            b->f();   // calls derived::f();
            b = &d2;
            b->f();   // calls derived2::f();
            return 0;
        }
        

        而且它不称为对象切片。对象切片是指将派生类对象分配给基类对象(而不是指针),其中派生类对象的成员被切片,保留。因此,您无法从基类对象访问这些切片成员。

        (2) 除了使用 reinterpret_cast 的那一行之外,其他语句都很好。

        (3) 如果您使用对象或覆盖派生类函数来调用基类版本,则必须使用范围解析运算符(如其他答案中所述)。但是,您可以使用指针:

        derived *d = static_cast( &b1 );
        d->f();
        

        【讨论】:

          猜你喜欢
          • 2013-11-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-06-16
          • 1970-01-01
          • 2020-09-11
          • 1970-01-01
          相关资源
          最近更新 更多