GotW #5: 仮想関数のオーバーライド (勝手訳)
GotW #5 の翻訳です。
例によって原文著者(Herb Sutter 氏)の許可は得ていませんし、私の英訳がヒドいクオリティである(用語の統一がとれていないとか、誤訳が含まれているとか)かもしれませんのでそこのところはご理解いただければと思います。
原文:http://herbsutter.com/2013/05/22/gotw-5-solution-overriding-virtual-functions/
Virtual functions are a pretty basic feature, but they occasionally harbor subtleties that trap the unwary. If you can answer questions like this one, then you know virtual functions cold, and you’re less likely to waste a lot of time debugging problems like the ones illustrated below.
仮想関数はとても基本的な機能ですが、ちょっとした引っ掛けをはらんでいます。今回のような問題に答えられるようであれば、あなたは仮想関数について完璧に知っていて、以下で説明されているような問題をデバッグするのに何時間もかけてしまうようなことはないでしょう。
Problem 問題
JG Question 見習いグルへの質問
1. What do the override and final keywords do? Why are they useful?
1. override および final キーワードは何をするか?それらはなぜ有用か?
Guru Question グルへの質問
2. In your travels through the dusty corners of your company’s code archives, you come across the following program fragment written by an unknown programmer. The programmer seems to have been experimenting to see how some C++ features worked.
(a) What could be improved in the code’s correctness or style?
(b) What did the programmer probably expect the program to print, but what is the actual result?
2. あなたはあなたの会社のコードアーカイブの埃をかぶった一角を旅している。あなたは、以下のような誰が書いたかわからないプログラムの断片に行き当たった。そのプログラムを書いたプログラマはいくつかのC++の機能がどのように動くのか、実験していたかのようだ。
(a) コードの正しさやスタイルを改善するとしたら何ができるか?
(b) そのプログラマはこのプログラムが印字することを期待していたのだと思うが、実際には何が起こるか?
class base { public: virtual void f( int ); virtual void f( double ); virtual void g( int i = 10 ); }; void base::f( int ) { cout << "base::f(int)" << endl; } void base::f( double ) { cout << "base::f(double)" << endl; } void base::g( int i ) { cout << i << endl; } class derived: public base { public: void f( complex<double> ); void g( int i = 20 ); }; void derived::f( complex<double> ) { cout << "derived::f(complex)" << endl; } void derived::g( int i ) { cout << "derived::g() " << i << endl; } int main() { base b; derived d; base* pb = new derived; b.f(1.0); d.f(1.0); pb->f(1.0); b.g(); d.g(); pb->g(); delete pb; }
Solution 解答
1. What do the override and final keywords do? Why are they useful?
1. override および final キーワードは何をするか?それらはなぜ有用か?
These keywords give explicit control over virtual function overriding. Writing override declares the intent to override a base class virtual function. Writing final makes a virtual function no longer overrideable in further-derived classes, or a class no longer permitted to have further-derived classes.
これらのキーワードは、仮想関数のオーバーライドに対して明示的な制御を与えます。override と書くことは、基底クラスの仮想関数をオーバーライドする意図を宣言します。final と書くことは、仮想関数が派生クラスでオーバーライドされないこと、もしくはクラスがそれ以上継承できないようにします。
They are useful because they let the programmer explicitly declare intent in a way the language can enforce at compile time. If you write override but there is no matching base class function, or you write final and a further-derived class tries to implicitly or explicitly override the function anyway, you get a compile-time error.
これらはプログラマに明示的に意図を宣言させ、言語がコンパイル時に強制できるので有用です。あなたが override と書いたのにマッチする基底クラスの関数が存在しなければ、もしくはあなたが final と書いたのに派生クラスで暗黙的もしくは明示的にかかわらず関数をオーバーライドしたら、コンパイル時エラーになります。
Of the two, by far the more commonly useful is override; uses for final are rarer.
これら2つのうち、override のほうが一般的にははるかに有用です。final を使うのはそれに比べると稀です。
2. (a) What could be improved in the code’s correctness or style?
2.(a) コードの正しさやスタイルを改善するとしたら何ができるか?
First, let’s consider some style issues, and one real error:
まず、いくつかあるスタイル上の問題を考えてみましょう。そのうちの一つは実際にエラーです:
1. The code uses explicit new, delete, and an owning *.
1. コードは明示的な new と delete、所有する * を使っている
Avoid using owning raw pointers and explicit new and delete except in rare cases like when you’re writing the internal implementation details of a low-level data structure.
生ポインタを所有したり、new や delete を使うことは避けなければなりません。ただし、低レベルなデータ構造の内部の詳細な実装を書いているというレアケースのような場合は除きます。
{ base* pb = new derived; ... delete pb; }
Instead of new and base*, use make_unique and unique_ptr
new と base* の代わりに、make_unique と unique_ptr
を使います。
{ auto pb = unique_ptr<base>{ make_unique<derived>() }; ... } // automatic delete here
Guideline: Don’t use explicit new, delete, and owning * pointers, except in rare cases encapsulated inside the implementation of a low-level data structure.
ガイドライン:明示的な new、delete および所有権のある * ポインタを使用しない。低レベルデータ構造の実装の隠蔽された中身のようなレアケースを除く。
However, that delete brings us to another issue unrelated to how we allocate and manage the lifetime of the object, namely:
しかしながら、delete はオブジェクトの確保や生存期間の管理の方法に関係なく、別の問題を持ち込みます。すなわち:
2. base’s destructor should be virtual or protected.
2. base のデストラクタは virtual もしくは protected であるべき。
class base { public: virtual void f( int ); virtual void f( double ); virtual void g( int i = 10 ); };
This looks innocuous, but the writer of base forgot to make the destructor either virtual or protected. As it is, deleting via a pointer-to-base without a virtual destructor is evil, pure and simple, and corruption is the best thing you can hope for because the wrong destructor will get called, derived class members won’t be destroyed, and operator delete will be invoked with the wrong object size.
これは無害に見えますが、base を書いた人はデストラクタを virtual にするか protected にすることを忘れています。このまま virtual デストラクタなしで base へのポインタ経由で delete することははっきりいって悪です。こうなるとあなたが望むことのできる最良のものは破滅です。間違ったデストラクタが呼ばれ、derived クラスのメンバが破棄されず、operator delete が間違ったオブジェクトサイズで起動されます。
Guideline: Make base class destructors public and virtual, or protected and nonvirtual.
ガイドライン:基底クラスのデストラクタを public で virtual にするか、protected で 非 virtual にせよ。
Exactly one of the following can be true for a polymorphic type:
ポリモーフィック型において、以下のうちいずれかのみが真となり得ます。
- Either destruction via a pointer to base is allowed, in which case the function has to be public and had better be virtual;
- or else it isn’t, in which case the function has to be protected (private is not allowed because the derived destructor must be able to invoke the base destructor) and would naturally also be nonvirtual (when the derived destructor invokes the base destructor, it does so nonvirtually whether declared virtual or not).
- 基底クラスへのポインタ経由での破棄が許されているならば、デストラクタは public かつ virtual である必要がある;
- 基底クラスへのポインタ経由での破棄が許されていないのであれば、デストラクタは protected とし(private であってはなりません。派生クラスのデストラクタは基底クラスのデストラクタを起動しなければならないためです)、必然的に非 virtual とします(派生クラスのデストラクタが基底クラスのデストラクタを起動したとき、virtual と宣言されているかどうかにかかわらず、それは非 virtual 的に動作します)。
Interlude 間奏
For the next few points, it’s important to differentiate three terms:
次の点について、3つの用語を区別しておくことは重要です。
- To overload a function f means to provide another function with the same name in the same scope but with different parameter types. When f is actually called, the compiler will try to pick the best match based on the actual parameters that are supplied.
- To override a virtual function f means to provide another function with the same name and the same parameter types in a derived class.
- To hide a function f that exists in an enclosing scope (base class, outer class, or namespace) means to provide another function with the same name in an inner scope (derived class, nested class, or namespace), which will hide the same function name in an enclosing scope.
3. derived::f is neither an override nor an overload.
3. derived::f はオーバーライドでもオーバーロードでもない
void derived::f( complex<double> )
derived does not overload the base::f functions, it hides them. This distinction is very important, because it means that base::f(int) and base::f(double) are not visible in the scope of derived.
derived はいずれの base::f 関数もオーバーロードしません。それらを隠してしまいます。この区別はとても重要です。base::f(int) と base::f(double) は derived のスコープでは不可視となってしまうことを意味するからです。
If the author of derived intended to hide the base functions named f, then this is all right. Usually, however, the hiding is inadvertent and surprising, and the correct way to bring the names into the scope of derived is to write the using-declaration using base::f; inside derived.
derived を書いた人が f という名前の基底クラスの関数を意図して隠したのであればこれは問題ありません。しかし普通は隠すことは意図的ではなく驚きです。derived クラスのスコープにそれらの名前を持ち込むための正しい方法は、derived の中に using 宣言 using base::f; を書くことです。
Guideline: When providing a non-overridden function with the same name as an inherited function, be sure to bring the inherited functions into scope with a using-declaration if you don’t want to hide them.
ガイドライン: 継承された関数と同じ名前で非オーバーライド関数を提供する場合、継承されるはずの関数を隠したくなければ using 宣言を使ってスコープの中にそれらを確実に取り込む。
4. derived::g overrides base::g but doesn’t say “override.”
4. derived::g は base::g をオーバーライドしているが、override と書いていない。
void g( int i = 20 ) /* override */
This function overrides the base function, so it should say override explicitly. This documents the intent, and lets the compiler tell you if you’re trying to override something that’s not virtual or you got the signature wrong by mistake.
この関数は base の関数をオーバーライドしますので、明示的に override と宣言すべきです。これはその意図をドキュメント化し、あなたが非virtual な何かをオーバーライドしようとしていることや誤って違うシグネチャを書いてしまったことをコンパイラに検出させることができます。
Guideline: Always write override when you intend to override a virtual function.
ガイドライン:仮想関数をオーバーライドしようとしているなら、常に override と書く。
5. derived::g overrides base::g but changes the default argument.
5. derived::g は base::g をオーバーライドしているが、デフォルト引数を変更している。
void g( int i = 20 )
Changing the default argument is decidedly user-unfriendly. Unless you’re really out to confuse people, don’t change the default arguments of the inherited functions you override. Yes, this is legal C++, and yes, the result is well-defined; and no, don’t do it. Further below, we’ll see just how confusing this can be.
デフォルト引数を変更することは決定的にユーザに不親切です。もしわざと人々を混乱させようとしているのでなければ、オーバーライドする関数のデフォルト引数を変更しないでください。これは C++ 的に合法であり、結果もきちんと定義されています。しかし、絶対にしないでください。この後で、これがいかに混乱を招くかを見ていきます。
Guideline: Never change the default arguments of overridden inherited functions.
ガイドライン:オーバーライドされた関数のデフォルト引数を変更してはならない。
We could go one step further:
さらに一歩推し進めて:
Guideline: Avoid default arguments on virtual functions in general.
ガイドライン:一般に、仮想関数ではデフォルト引数の使用を避ける
Finally, public virtual functions are great when a class is acting as a pure abstract base class (ABC) that only specifies the virtual interface without implementations, like a C# or Java interface does.
最後に、public な仮想関数は、そのクラスが純粋な抽象基底クラス(Abstract base class: ABC)となるときには素晴らしいものです。純粋な抽象基底クラスとは、実装のない仮想インターフェースのみが指定されるもので、C# や Java の interface のようなものです。
Guideline: Prefer to have a class contain only public virtual functions, or no public virtual functions (other than the destructor which is special).
ガイドライン: クラスには public な仮想関数だけを持たせる、もしくは public な仮想関数を全く持たせない(デストラクタだけは特別なので除外)のいずれかとせよ。
A pure abstract base class should have only public virtual functions. …
純粋な抽象基底クラスは public な仮想関数だけを持つべきです...
But when a class is both providing virtual functions and their implementations, consider the Non-Virtual Interface pattern (NVI) that makes the public interface and the virtual interface separate and distinct.
しかし、あるクラスが仮想関数とその実装をともに提供するとき、非仮想インターフェース(NVI: Non-Virtual Interface)パターンを考慮してください。public なインターフェースと仮想関数を分離して区別できるようにします。
… For any other base class, prefer making public member functions non-virtual, and virtual member functions non-public; the former should have any default arguments and can be implemented in terms of the latter.
... それ以外の基底クラスは、public なメンバー関数は非 virtual とし、仮想メンバ関数は非 public とします;デフォルト引数は前者にもたせ、また前者は後者を使って実装します。
This cleanly separates the public interface from the derivation interface, lets each follow its natural form best suited for its distinct audience, and avoids having one function exist in tension from doing double duty with two responsibilities. Among other benefits, using NVI will often clarify your class’s design in important ways, including for example that the default arguments which matter to the caller therefore naturally belong on the public interface, not on the virtual interface. Following this pattern means that several classes of potential problems, including this one of virtuals with default arguments, just naturally don’t arise.
これにより public インターフェースを派生インターフェースから明確に分離し、利用者にとってベストで自然な形にすることができますし、一つの関数に2つの責任を負わせて2つの仕事をさせるのを回避することができます。数ある利点の中でも特に、NVI を使うことはあなたのクラスのデザインを重要な点で明確にすることがあります。たとえば呼び出し元にとって問題となるデフォルト引数が仮想インターフェースではなく public インターフェースに自然に属しているといったようなことです。このパターンに従うことで、潜在的な問題となるようなクラス(このデフォルト引数付きの仮想関数をもったものなど)が単純に発生しなくなります。
The C++ standard library follows NVI nearly universally, and other modern OO languages and environments have rediscovered this principle for their own library design guidelines, such as in the .NET Framework Design Guidelines.
C++ 標準ライブラリはほぼすべての箇所で NVI に従っています。また他のモダンな OO 言語や処理系もそのライブラリのデザインガイドラインにおいてこの原則を再発見しています。たとえば .NETのクラスライブラリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Microsoft.net Development Series)
2. (b) What did the programmer probably expect the program to print, but what is the actual result?
2. (b) そのプログラマはこのプログラムが印字することを期待していたのだと思うが、実際には何が起こるか?
Now that we have those issues out of the way, let’s look at the mainline and see whether it does that the programmer intended:
これまでの問題では本筋から外れてしまっていましたが、いよいよ本流に戻ってそのプログラマが何を意図していたのかを見ていきましょう。
int main() { base b; derived d; base* pb = new derived; b.f(1.0);
No problem. This first call invokes base::f( double ), as expected.
これは問題ないですね。最初の呼び出しは base::f(double) を期待通り呼び出します。
d.f(1.0);
This calls derived::f( complex
これは derived::f(complex
) を呼び出します。なぜでしょうか?derived が f という名前の base の関数をスコープに持ち込むための using base::f; を宣言していなかったことを思い出してください。ということで、明らかに base::f(int) と base::f(double) は呼び出されません。それらは derived::f(complex ) と同じスコープには存在しないので、オーバーロードの対象になりません。
The programmer may have expected this to call base::f( double ), but in this case there won’t even be a compile error because fortunately(?) complex
そのプログラマは base::f(double) が呼び出されることを期待していたかもしれませんが、このケースでは幸運なことに(?)コンパイルエラーにすらなりません。complex
が double からの暗黙の変換を提供していてコンパイラが derived::f(complex (1.0)) を呼び出すように解釈するからです。
pb->f(1.0);
Interestingly, even though the base* pb is pointing to a derived object, this calls base::f( double ) because overload resolution is done on the static type (here base), not the dynamic type (here derived). You have a base pointer, you get the base interface.
面白いことに、base* pb は derived オブジェクトを指しているにもかかわらず、これは base::f(double) を呼び出します。オーバーロード解決が静的型(ここでは base)に対して行われるからで、動的型(ここでは derived)に対して行われるのではないためです。base ポインタを使うと、base のインターフェースが得られるというわけです。
For the same reason, the call pb->f(complex
同じ理由で、pb->f(complex
(1.0)); はコンパイルされません。base インターフェースにおいて、これを満たす関数は存在しないからです。
b.g();
This prints 10, because it simply invokes base::g( int ) whose parameter defaults to the value 10. No sweat.
これは 10 を出力します。単純に base::g(int) を起動してそのデフォルト引数が 10 だからです。特に面白くありませんね。
d.g();
This prints derived::g() 20, because it simply invokes derived::g( int ) whose parameter defaults to the value 20. Also no sweat.
これは derived::g() 20 を出力します。単純に derived::g(int) を起動してそのデフォルト引数が 20 だからです。これも特に面白くありません。
pb->g();
This prints derived::g() 10.
これは derived::g() 10 を出力します。
“Wait a minute!” you might protest. “What’s going on here?” This result may temporarily lock your mental brakes and bring you to a screeching halt until you realize that what the compiler has done is quite proper. (Although, of course, the programmer of derived ought to be taken out into the back parking lot and yelled at.) The thing to remember is that, like overloads, default parameters are taken from the static type (here base) of the object, hence the default value of 10 is taken. However, the function happens to be virtual, and so the function actually called is based on the dynamic type (here derived) of the object. Again, this can be avoided by avoiding default arguments on virtual functions, such as by following NVI and avoiding public virtual functions entirely.
"ちょっと待って!" とあなたは抗議するかもしれません。"いったい何が起こっているの?" この結果は、コンパイラが何をしたのかを正しく理解できるまで一時的にあなたの精神のブレーキをロックしてキキーーッと止まらせてしまうかもしれません。(とはいえ、もちろんその derived を書いたプログラマは裏の駐車場に引きずり出されて怒鳴りつけられるべきでしょうね)覚えておくべきことは、オーバーロードのように、デフォルトパラメータはオブジェクトの静的型(ここでは base)から取られるということで、したがってデフォルト値の 10 が取られます。しかしながら、この関数は virtual として呼び出され、実際に呼び出される関数は動的型(ここでは derived)のオブジェクトにもとづいています。もう一度言いますが、これは NVI に従ったり public な仮想関数を全面的に回避するといったことによってデフォルト引数を仮想関数に与えるのを回避することで回避できます。
delete pb;
}
Finally, as noted, this shouldn’t be needed because you should be using unique_ptrs which do the cleanup for you, and base should have a virtual destructor so that destruction via any pointer to base is correct.
最後に、すでに述べたように、これは必要とされるべきではありません。unique_ptr を使うべきだからです。unique_ptr があなたのために後片付けをしてくれます。また、base は仮想デストラクタを持つべきです。base への任意のポインタを介してデストラクションが正しく行われるようにするためです。
Acknowledgments 謝辞
Thanks in particular to the following for their feedback to improve this article: litb1, KrzaQ, mttpd.
この記事をより良くするための以下の各位からのフィードバックに対し、特に感謝する:litb1, KrzaQ, mttpd