2

私は試験のために C++ を勉強しており、C++ の一般的な誤解のほとんどをかなり疲れて理解したと思っていましたが、過去の試験で私を夢中にさせている演習に遭遇しました。それはある意味で仮想メソッドと継承を組み合わせたものです。ここで理解していないように見えるのはコードです:

    #include <iostream>

    class B;

class A {
    public:
    virtual A* set(A* a) = 0;
};

class B : public A {
    public:
    virtual A* set(B* b) {
            std::cout << "set1 has been called" << std::endl;
            b = this;
            return b;
    }

    virtual B* set(A* a) {
            std::cout << "set2 has been called" << std::endl;
            a = this;
            return this;
    }
};

int main(int argc, char *argv[]) {
    B *b = new B();
    A *a = b->set(b);
    a = b->set(a);
    a = a->set(b);
    a = a->set(a);
    return 0;
}

出力は

set1 has been called
set2 has been called
set2 has been called
set2 has been called

私が収集したものから、最初の呼び出し (b->set(b) ) はクラス B の最初のメソッドを呼び出し、b 自体を返します。次に、この objectref が A にキャストされ、オブジェクト b が現在型 A であることを意味しますか? だから私は A *a = A *b; を持っています。objectoftypeA->set(objectoftypeA)2つのオブジェクトが基本クラスであるため、仮想メソッドを調べる必要はありません。

とにかく、ご覧のとおり、私は多くの混乱を抱えているので、愚かなエラーを犯した場合はご容赦ください。誰かがこのコードで何が起こっているかを説明できれば幸いです。ウェブを検索しようとしましたが、問題を引き起こさない小さくて簡単な例しか見つかりませんでした.

4

2 に答える 2

4

このプログラムは、メンバー関数がどのように検索されるかを示しています。オブジェクトの静的な型によって、呼び出される関数のオーバーロードが決まります。つまり、名前の検索が実行されます。次に、動的タイプによって、呼び出される仮想オーバーライドが決定されます。

おそらく重要な点は、同じ名前の異なるオーバーロードが実際には異なる機能であるということです。

Aのメンバーは 1 つしかないため、引数が何であれ、setを呼び出したときに発生する可能性があることは 1 つだけです。a->set()しかし、 を呼び出すとb->set()、いくつかの潜在的な関数があり、最適なものが選択されます。

B::setは決してオーバーライドされないため、オーバーライドされているかどうかに違いはありませvirtualん。virtual同じクラスのメンバーはお互いにまったく話しません。

于 2012-07-08T14:38:18.600 に答える
2

Potatoswatter is right, but I think I have a bit "clearer" explanation. I think the OP is getting confused on what happens at run-time with dynamic type lookup versus compile-time, and when up-casting happens automatically, versus when it does not.

First off, return type does NOT affect which overload is called. You probably know that, but it bears repeating. A return type mis-match will cause an error at compile-time, but not run-time, and does not affect which overload is called. Also it's worth noting that as long as it is a compatible pointer type (in a hierarchy together) returning a pointer doesn't ever "change" it. It is still the same pointer, unlike converting floats to ints, where there is an actual change.

Now to go through the calls one-by-one. This is my understanding of the process, not necessarily what the standard, or what "really" happens.

When you call b->set(b) the compiler (not run-time) goes "looking for a method named set with an argument of pointer to B" which it finds with the one that outputs set1. It's virtual, so there's code to check if the class points to anything lower, but there isn't, so it just calls it, and returns the this pointer into a.

Now you're calling b->set(a). Again it's the compiler that goes "does b have an overload that takes pointer to A?" Yes it does, so it calls the "set2" method. It's the compiler that sees an A* and so the call is "determined" at that point. Even though the pointer points to an object that is of type B, the compiler doesn't know that, or care. So it's the compile-time types of the arguments that determine which overloaded method get taken. From that point on, where in the hierarchy the virtual gets taken is on the underlying type of the this pointer, but only downward.

Here's a different case though. Try this: b->set(dynamic_cast<B*>(a)) This should call the "set1" method, because the compiler is going to definitely have a pointer to B (even if it's nullptr).

Now the third case: a->set(b). What's happening here is the compiler says "there is only one set method, so can the argument be up-cast or constructed to that type?" The answer is yes, as B is a child of A. So that cast happens transparantly, and the compiler calls the ABSTRACT dispatcher for the set method of the type A. This occurs at compile time before the "real" type of what a is pointer to. Then at run-time, the program "walks the virtual" and finds the lowest one, the B->set(A*) method that emits "set2". The actual type of what the argument points to isn't used, only the type to the left of the arrow operator, and that only determines how far down the hierarchy.

And the fourth case is just the 3rd again. The type of the argument (the pointer, not whta is pointed to) is compatible, so it goes as before. If you want a dramatic demonstration of this, try this:

a->set((A*)nullptr) // prints "set2 has been called"
b->set((A*)nullptr) // prints "set2 has been called"
b->set((B*)nullptr) // prints "set1 has been called"

The underlying type of what the arguments point to doesn't affect dynamic dispatch. Only their "surface" type affects the overload called.

于 2012-07-08T16:00:39.757 に答える