简体   繁体   English

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

[英]Polymorphism in C++: Calling an overridden method

First, I'm Java coder and want to understand polymorphism in c++. 首先,我是Java编码器,想要了解c ++中的多态性。 I wrote the example for learning purposes: 我为了学习目的编写了这个例子:

#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

I expected that overriden foo would be printed, but it wasn't. 我希望打印overriden foo ,但事实并非如此。 Why? 为什么? We overrode the method foo in the class B and I thought that the decision which method should be called is being making from the runtime type of the object which in my case is B , but not a static type ( A in my case). 我们覆盖了class B的方法foo ,我认为决定应该调用哪个方法是从对象的运行时类型开始,在我的例子中是B ,但不是静态类型(在我的例子中是A )。

Live example is there 现场的例子就在那里

When you do this: 当你这样做:

A c = B(); 

You're converting the B value into A . 转换 B值成A You don't want that. 你不希望这样。

You should make a B object and access it through an A pointer or reference to get polymorphic behaviour: 您应该创建一个B对象并通过A 指针或引用访问它以获得多态行为:

B b;
A& c = b;

In java, you have value semantics with types like int and float , and you have reference semantics with everything else. 在java中,您具有类型为intfloat 值语义 ,并且您具有其他所有内容的引用语义

That's not the case in C++ : the type system is unified, and you get whichever of value or reference semantics that you ask for . C++并非如此:类型系统是统一的,您可以获得您要求的任何值或引用语义。

With the code you've written 用你编写的代码

A c = B()

you've told the compiler to create a new value of type B , and then convert it to a value of type A , storing the value in c . 你告诉编译器创建一个类型B的新值,然后转换为类型A ,将存储在c Conversion, in this case, means taking A data out of the new B instance you created it, and copying it into the new A instance stored in c . 在这种情况下,转换意味着将A数据从您创建它的新B实例中取出,并将其复制到存储在c的新A实例中。

You could do this instead: 你可以这样做:

B b;
A &c = b;

This still creates the value b , but now c is a reference to A , which means c will now refer to the B instance you created, rather than being a copy of its A part. 这仍然会创建 b ,但现在c是对A引用 ,这意味着c现在将引用您创建的B实例,而不是其A部分的副本。

Now, this still creates b as a local variable, and the object stored in b gets destroyed as soon as b goes out of scope. 现在,这仍然创建b作为局部变量,并且一旦b超出范围,存储在b的对象就会被销毁。 If you wanted something more persistent, you'd need to use pointers ; 如果你想要更持久的东西,你需要使用指针 ; eg something like 例如

shared_ptr<A> c = make_shared<B>();
c->foo();

You could do something more 'raw' like 你可以做更像'原始'的事情

A *b = new B();

but this is a 'dumb' pointer; 但这是一个“愚蠢”的指针; shared_ptr is smarter and your object will get destroyed when nothing else references it. shared_ptr更聪明,当没有其他东西引用它时,你的对象将被销毁。 If you do the latter, you'd have to do the destruction yourself when appropriate (and messing this up is a common source of mistakes) 如果你做了后者,你必须在适当的时候自己做破坏(并且弄乱它是一个常见的错误来源)

Your confusion stems from a crucial difference between Java and C++. 您的困惑源于Java和C ++之间的重要区别。

In Java if you write 在Java中,如果你写

MyClass var = whatever;

your variable var is a reference to the object returned by whatever . 你的变量var whatever返回的对象的引用 However, in C++ this syntax means "create a new object of type MyClass by passing the result of the expression whatever to an appropriate constructor, and copy the resulting object into the variable var . 然而,在C ++中此语法表示“创建类型的新对象MyClass通过将表达式的结果whatever一种适当的构造,并且将所得对象复制到变量var

In particular, your code creates a new object of type A , named c , and passes a temporary default-constructed object of type B to its copy constructor (because that's the only constructor that fits). 特别是,您的代码创建了A名为c的类型为A的新对象,并将类型为B的临时默认构造对象传递给其复制构造函数(因为这是唯一适合的构造函数)。 Since the newly created object is of type A , not of type B , obviously A 's method foo is called. 由于新创建的对象是类型A而不是类型B ,因此显然调用了A的方法foo

If you want to have a reference to an object, you have to explicitly request that in C++, by adding & to the type. 如果要引用对象,则必须在C ++中通过向类型添加&来显式请求。 However a reference to non-constant objects cannot be bound to temporaries. 但是,对非常量对象的引用不能绑定到临时对象。 therefore you need to explicitly declare also the object you bind to (or alternatively, use a reference to a const object, and fix your foo member functions to be const , since they don't change the object anyway). 因此,您还需要显式声明绑定的对象(或者,使用对const对象的引用,并将foo成员函数修复为const ,因为它们无论如何都不会更改对象)。 So the simplest version of your code doing what you want would read: 因此,最简单的代码版本可以执行您想要的内容:

// 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

However the better version would be const-correct, and then could use your original construction: 然而,更好的版本将是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

(note that I removed using namespace std; because it's a bad thing to do (and your code used explicit std:: anyway, so it's just redundant). (注意我using namespace std;删除了using namespace std;因为这是一件坏事(你的代码使用显式的std::反正,所以它只是多余的)。

Note however, that C++ references are still different from Java references in that they cannot be reassigned; 但请注意,C ++引用仍然与Java引用不同,因为它们无法重新分配; any assignment goes to the underlying object instead. 任何赋值都转到底层对象。 For example: 例如:

#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"
}

If you want to change the object you refer to, you'll have to use pointers: 如果要更改引用的对象,则必须使用指针:

// 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"
}

The line of interest is this (using uniform initialization syntax instead): 感兴趣的是这个(使用统一的初始化语法):

A c = B{};

It is important to note that, when declared this way, c behaves like a value. 重要的是要注意,当以这种方式声明时, c行为类似于值。

Your code constructs a local A named c out of an instance of B . 您的代码构造了B实例中名为c的本地A This is called slicing: Any part of that B that isn't also part of an A has been "sliced" away, leaving only an A . 这称为切片: B中不属于A任何部分已被“切掉”,仅留下A

Giving a symbol reference semantics in C++ (called indirection) requires a different notation. 在C ++中给出符号引用语义(称为间接)需要不同的表示法。

For example: 例如:

A &c = B{};
A d = B{}; // Casts the intermediate B instance to const A &,
           // then copy-constructs an A

c.foo(); // calls B::foo because c points to a B through an A interface

d.foo(); // calls A::foo because d is only an instance of an A

Note that the lifetime of the intermediate B to which c points is automatically extended to the scope of c . 注意, c点的中间B的寿命自动扩展到c的范围。 On the other hand, the second intermediate B is destroyed after the construction of d has completed. 另一方面,在完成d的构造之后,第二中间体B被破坏。

In C++, references are immutable (they cannot be changed after initialization). 在C ++中,引用是不可变的(初始化后不能更改它们)。 When used in an expression, it is as though the object (value) to which they are pointing were used instead: 在表达式中使用时,就好像它们所指向的对象(值)一样:

A &c = B{};

c = A{}; // Calls the compiler-provided A::operator = (const A &)
         // (a virtual assignment operator with this signature
         // was not defined).
         // This DOES NOT change where c points.

Pointers, on the other hand, can be changed: 另一方面,指针可以改变:

A a{};
B b{};

A *cptr = &b;

cptr->foo(); // calls B::foo

cptr = &a;

cptr->foo(); // calls A::foo

exactly what orlp said. 究竟是什么orlp说的。 you should also learn to use pointers too (they are fun) 你也应该学会使用指针(它们很有趣)

A* c = new B();
c->foo(); // overriden foo
delete c;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM