C++中的多态性:调用重写方法

ste*_*lla 15 c++ polymorphism

首先,我是Java编码器,想要了解c ++中的多态性.我为了学习目的编写了这个例子:

#include<iostream>

using namespace std;

class A
{
public:
    virtual void foo(){ std::cout << "foo" << std::endl; }
};

class B : public A
{
public:
    void foo(){ std::cout << "overriden foo" << std::endl; }
};

A c = B(); 

int main(){ c.foo(); } //prints foo, not overriden foo
Run Code Online (Sandbox Code Playgroud)

我预计overriden foo会打印,但事实并非如此.为什么?我们覆盖了该方法foo,class B并且我认为决定应该调用哪个方法是从对象的运行时类型开始,在我的例子中B,但不是静态类型(A在我的例子中).

现场的例子就在那里

orl*_*rlp 22

当你这样做:

A c = B(); 
Run Code Online (Sandbox Code Playgroud)

转换B价值为A.你不希望这样.

您应该创建一个B对象并通过A 指针或引用访问它以获取多态行为:

B b;
A& c = b;
Run Code Online (Sandbox Code Playgroud)


小智 14

在java中,你有类似和的类型的值语义,并且你有其他所有的引用语义.intfloat

情况并非如此C++:类型系统是统一的,您可以获得您要求的任何值或引用语义.

用你编写的代码

A c = B()
Run Code Online (Sandbox Code Playgroud)

你告诉编译器创建类型的新值B,然后转换它类型的值A,存储的价值c.在这种情况下,转换意味着AB您创建它的新实例中获取数据,并将其复制A存储在其中的新实例中c.

你可以这样做:

B b;
A &c = b;
Run Code Online (Sandbox Code Playgroud)

这仍然创造了价值 b,但现在c是一个参考A,这意味着c现在将参照B创建,而不是它的一个副本实例A的一部分.

现在,这仍然b作为局部变量创建,并且存储的对象b一旦b超出范围就会被销毁.如果你想要更持久的东西,你需要使用指针 ; 例如

shared_ptr<A> c = make_shared<B>();
c->foo();
Run Code Online (Sandbox Code Playgroud)

你可以做更像'原始'的事情

A *b = new B();
Run Code Online (Sandbox Code Playgroud)

但这是一个"愚蠢"的指针; shared_ptr更聪明,当没有别的东西引用它时你的物体就会被摧毁.如果你做了后者,你必须在适当的时候自己做破坏(并且弄乱它是一个常见的错误来源)


cel*_*chk 7

您的困惑源于Java和C++之间的重要区别.

在Java中,如果你写

MyClass var = whatever;
Run Code Online (Sandbox Code Playgroud)

您的变量var返回的对象的引用whatever.但是,在C++中,此语法意味着" 通过将表达式的结果传递给适当的构造函数来创建类型MyClass新对象whatever,并将结果对象复制到变量中var.

特别是,您的代码创建了一个A名为的新类型对象,并将类型c的临时默认构造对象传递B给其复制构造函数(因为这是唯一适合的构造函数).由于新创建的对象是类型A而不是类型B,因此显然调用了A方法foo.

如果要引用对象,则必须通过添加&类型在C++中明确请求.但是,对非常量对象的引用不能绑定到临时对象.因此,您还需要显式声明绑定的对象(或者,使用对const对象的引用,并修复您的foo成员函数const,因为它们无论如何都不会更改对象).因此,最简单的代码版本可以执行您想要的内容:

// your original definitions of A and B assumed here

B b; // The object of type B
A& c = b; // c is now a *reference* to b
int main() { c.foo(); } // calls B::foo() thanks to polymorphism
Run Code Online (Sandbox Code Playgroud)

然而,更好的版本将是const-correct,然后可以使用您的原始构造:

#include <iostream>

class A
{
public:
    virtual void foo() const  // note the additional const here!
    { std::cout << "foo" << std::endl; }
};

class B : public A
{
public:
    void foo() const // and also const here
    { std::cout << "overridden foo" << std::endl; }
};

A const& c = B(); // Since we bind to a const reference,
                  // the lifetime of the temporary is extended to the
                  // lifetime of the reference

int main() { c.foo(); } //prints overridden foo
Run Code Online (Sandbox Code Playgroud)

(注意我删除了,using namespace std;因为这是一件坏事(你的代码std::无论如何都是显式的,所以它只是多余的).

但请注意,C++引用仍然与Java引用不同,因为它们无法重新分配; 任何赋值都转到底层对象.例如:

#include <iostream>

class A { public: virtual void foo() const { std::cout << "I'm an A\n"; } };
class B: public A { public: void foo() const { std::cout << "I'm a B\n"; } };
class C: public A { public: void foo() const { std::cout << "I'm a C\n"; } };

B b;
C c;

int main()
{
   A& ref = b; // bind reference ref to object b
   ref.foo(); // outputs "I'm a B"
   ref = c; // does *not* re-bind the reference to c, but calls A::operator= (which in this case is a no-op)
   ref.foo(); // again outputs "I'm a B"
}
Run Code Online (Sandbox Code Playgroud)

如果要更改引用的对象,则必须使用指针:

// definitions of A, B and C as above
int main()
{
  A* prt = &b; // pointer ptr points to b
  prt->foo();  // outputs "I'm a B"
  prt = &c;    // reassign ptr to point to c
  prt->foo();  // outputs "I'm a C"
}
Run Code Online (Sandbox Code Playgroud)