(Translated by https://www.hiragana.jp/)
多重定義 - Wikipedia コンテンツにスキップ

多重たじゅう定義ていぎ

出典しゅってん: フリー百科ひゃっか事典じてん『ウィキペディア(Wikipedia)』

多重たじゅう定義ていぎ (たじゅうていぎ) あるいは オーバーロード (えい: overload) とは、プログラミング言語げんごにおいて同一どういつ名前なまえ(シンボル)を関数かんすうあるいはメソッドおよび同一どういつ演算えんざん記号きごうについて複数ふくすう定義ていぎし、利用りようにプログラムの文脈ぶんみゃくおうじて選択せんたくすることで複数ふくすう動作どうさおこなわせる仕組しくみである。 たとえば整数せいすうがた浮動ふどう小数点しょうすうてんがた複素数ふくそすうかたについておなじ「abs」という関数かんすう定義ていぎして絶対ぜったいもとめる、かたごとに個々ここ意味いみ名前なまえIDかえ関数かんすう定義ていぎするなどがげられる。多重たじゅう定義ていぎする対象たいしょうおうじてそれぞれ関数かんすう多重たじゅう定義ていぎ[注釈ちゅうしゃく 1]演算えんざん多重たじゅう定義ていぎ[注釈ちゅうしゃく 2]、メソッドの多重たじゅう定義ていぎ[注釈ちゅうしゃく 3]ばれる。メソッドの多重たじゅう定義ていぎ特殊とくしゅなケースとして、コンストラクタ多重たじゅう定義ていぎがある。また、Common Lispなどでは、多重たじゅう定義ていぎ可能かのう関数かんすうとしてgeneric function(en:Generic function)がある(このgenericはジェネリックプログラミングのジェネリックである)。

動作どうさの「上書うわがき」を意味いみするオーバーライド[注釈ちゅうしゃく 4]とはまったくことなる概念がいねんである。オーバーライドは動的どうてきポリモーフィズムたい)に利用りようされる。

概要がいよう

[編集へんしゅう]

動作どうさ選択せんたくするさいもちいられる代表だいひょうてき文脈ぶんみゃく情報じょうほうとしては、型付かたつられたプログラミング言語げんごにおいては関数かんすう演算えんざん引数ひきすう演算えんざんならばオペランド)としてあたえられたしき変数へんすう関連付かんれんづけられたかた情報じょうほうもちいられる(まれではあるがもどかた利用りようできるプログラミング言語げんご存在そんざいする)。関数かんすう名称めいしょうとそれらのかた情報じょうほうくみわせたものをシグネチャぶが、プログラムないでシグネチャが唯一ゆいいつまれば、関数かんすうめいやメソッドめい演算えんざん記号きごう重複じゅうふくしていてもすべき対象たいしょう唯一ゆいいつ決定けっていすることができる。このような型付かたつけによる多重たじゅう定義ていぎは、暗黙あんもくかた変換へんかん[注釈ちゅうしゃく 5]あるいはかた強制きょうせい[注釈ちゅうしゃく 6])、継承けいしょう[注釈ちゅうしゃく 7]あるいは包含ほうがん[注釈ちゅうしゃく 8]総称そうしょうがた[注釈ちゅうしゃく 9]、あるいはパラメーターづけがた[注釈ちゅうしゃく 10]ならんでプログラミング言語げんごにおいてたいせい[注釈ちゅうしゃく 11]実現じつげんするためのひとつの手段しゅだんである。

理論りろんてきには関数かんすう名前なまえ演算えんざん記号きごうたんなる記号きごうであり、意味いみてき必然ひつぜんがあるわけではないので、これを反映はんえいして多重たじゅう定義ていぎゆるすプログラミング言語げんごでは多重たじゅう定義ていぎされた関数かんすう演算えんざん、メソッドの意味いみ動作どうさ定義ていぎはかなり自由じゆうおこなうことができる(演算えんざんについては構文こうぶん解析かいせき都合つごうじょう優先ゆうせん順位じゅんいなどが制限せいげんされる場合ばあいる)。とはいえ関数かんすうめいやメソッド、とく演算えんざん用法ようほうにはかく分野ぶんやおよびプログラミング言語げんごごと慣習かんしゅうそだっている場合ばあいがあり、著名ちょめい関数かんすうたとえば数学すうがく関数かんすうsinなど)やメソッド、演算えんざんたいして慣習かんしゅうとあまりにかけはなれた意味いみすなわ動作どうさ定義ていぎあたえるとプログラムの可読かどくせいいちじるしい低下ていかをもたらす可能かのうせいがあるので注意ちゅうい必要ひつようである。

デフォルト引数ひきすう(オプション引数ひきすう)をサポートしない言語げんごJavaや、バージョン4.0よりもまえC#など)では、多重たじゅう定義ていぎによってデフォルト引数ひきすう類似るいじ機能きのう実現じつげんすることができる。

言語げんごによる多重たじゅう定義ていぎのサポート

[編集へんしゅう]

関数かんすう多重たじゅう定義ていぎをサポートしない言語げんごでは、たとえ関数かんすう引数ひきすうかたかずによらずアルゴリズムすなわち本質ほんしつてき内容ないようがまったくおなじでも、引数ひきすうかたかずごとに関数かんすうをそれぞれ定義ていぎする場合ばあいおな名前なまえ使つかえず、引数ひきすうおうじた名前なまえをそれぞれける必要ひつようがあり、すときも引数ひきすうおうじて使つかける必要ひつようがある。

C言語げんごでのれい以下いかしめす。

#include <stdio.h>
#include <math.h>

float vector2f_length(float x, float y) { return sqrtf(x * x + y * y); }
double vector2d_length(double x, double y) { return sqrt(x * x + y * y); }
float vector3f_length(float x, float y, float z) { return sqrtf(x * x + y * y + z * z); }
double vector3d_length(double x, double y, float z) { return sqrt(x * x + y * y + z * z); }

int main(void)
{
  printf("%f\n", vector2f_length(1.0f, -1.0f));
  printf("%f\n", vector2d_length(1.0, 2.0));
  printf("%f\n", vector3f_length(1.0f, -1.0f, 1.0f));
  printf("%f\n", vector3d_length(1.0, 2.0, -1.0));
}

ベクトルのながさを計算けいさんする関数かんすうを、かたおよび次元じげんごとに命名めいめいしている。

一方いっぽう関数かんすう多重たじゅう定義ていぎをサポートする言語げんごでは、関数かんすうのシグネチャがことなればおな名前なまえ使つかうことができる。関数かんすうには本質ほんしつてき名前なまえだけをければよく、すときも引数ひきすうによらず一様いちよう記述きじゅつできる。

C++でのれい以下いかしめす。

#include <cstdio>
#include <cmath>

float vector_length(float x, float y) { return std::sqrt(x * x + y * y); }
double vector_length(double x, double y) { return std::sqrt(x * x + y * y); }
float vector_length(float x, float y, float z) { return std::sqrt(x * x + y * y + z * z); }
double vector_length(double x, double y, float z) { return std::sqrt(x * x + y * y + z * z); }

int main(void)
{
  printf("%f\n", vector_length(1.0f, -1.0f)); // (float, float) バージョンがばれる。
  printf("%f\n", vector_length(1.0, 2.0)); // (double, double) バージョンがばれる。
  printf("%f\n", vector_length(1.0f, -1.0f, 1.0f)); // (float, float, float) バージョンがばれる。
  printf("%f\n", vector_length(1.0, 2.0, -1.0)); // (double, double, double) バージョンがばれる。
}

なお、C++11規格きかくでは、2次元じげんベクトルのながさをもとめる標準ひょうじゅん関数かんすうとして、多重たじゅう定義ていぎされたstd::hypot()関数かんすう用意よういされている[1]C++17では3次元じげんベクトルバージョンも追加ついかされている。

ルックアップ

[編集へんしゅう]

多重たじゅう定義ていぎのルックアップ(えい: look-up探索たんさく)はじつ引数ひきすうかたおうじて静的せいてき解決かいけつされる。以下いかJavaれいでは、java.lang.Stringクラスはjava.lang.Objectクラスから派生はせいしているものの、testMethod()引数ひきすう動的どうてきかた情報じょうほう実行じっこうがた情報じょうほう)によって選択せんたくされることはなく、あくまでコンパイル解決かいけつされる静的せいてきかた情報じょうほうもとづいて選択せんたくされる。

public class Main {
    static void testMethod(String str) { System.out.println("String version is called."); }
    static void testMethod(Object obj) { System.out.println("Object version is called."); }
    public static void main(String[] args) {
        String str = "test";
        Object obj = str;
        testMethod(str); // String バージョンがばれる。
        testMethod(obj); // Object バージョンがばれる。
    }
}

なお、曖昧あいまいさを解決かいけつできず、多重たじゅう定義ていぎされた候補こうほなかから1つを選択せんたくすることができない場合ばあいはコンパイルエラーとなる。前述ぜんじゅつのC++のれいにおいて、曖昧あいまいさが解決かいけつできないケースを以下いかしめす。

  printf("%f\n", vector_length(1, -1)); // コンパイルエラー。
  printf("%f\n", vector_length(1.0f, -1.0)); // コンパイルエラー。
  printf("%f\n", vector_length(1.0, -1.0f)); // コンパイルエラー。

曖昧あいまいさの解決かいけつのためには明示めいじてきかた変換へんかん必要ひつようとなる。

  printf("%f\n", vector_length(double(1), double(-1))); // コンパイル可能かのう。(double, double) バージョンがばれる。

一方いっぽう前述ぜんじゅつのC言語げんごれいのように、多重たじゅう定義ていぎたず、曖昧あいまいさがない場合ばあい暗黙あんもくかた変換へんかん利用りようすることができる。

  printf("%f\n", vector2d_length(1, -1)); /* コンパイル可能かのう。 */
  printf("%f\n", vector2d_length(1.0f, -1.0)); /* コンパイル可能かのう。 */
  printf("%f\n", vector2d_length(1.0, -1.0f)); /* コンパイル可能かのう。 */

欠点けってん

[編集へんしゅう]

関数かんすう/メソッドおよび演算えんざん多重たじゅう定義ていぎされた場合ばあい、その名前なまえだけで区別くべつすることができないので、多重たじゅう定義ていぎ候補こうほのうち、どのバージョンが使つかわれるのかがソースコードじょう一見いっけんしてかりづらく、可読かどくせいがる。統合とうごう開発かいはつ環境かんきょう (IDE) のなかには、構文こうぶん解析かいせきによりどのバージョンがどこで使つかわれているかを列挙れっきょしてくれるものも存在そんざいするが、そういったツールが使つかえない状況じょうきょうでは詳細しょうさいなルックアップの知識ちしきがないと判別はんべつ困難こんなんなこともある。

多重たじゅう定義ていぎれい

[編集へんしゅう]

C++による多重たじゅう定義ていぎ[2]

// (1-1): 引数ひきすうかずちがいによる多重たじゅう定義ていぎ
int Function(void);
int Function( int value );
int Function( int value0, int value1 );

// (2): 引数ひきすう修飾しゅうしょくちがいによる多重たじゅう定義ていぎ
int Function( int *value );
int Function( int const *value );
int Function( int *const *value );
int Function( int *const *const *value );

// (3): 引数ひきすうかたちがいによる多重たじゅう定義ていぎ
int Function( char value );
int Function( std::complex< double > const &value );
int Function( ... ); // ※1
template< class Type > int Function( Type const &value ); // ※2

struct Example
{
    // (1-2): 引数ひきすうかたちがいによる多重たじゅう定義ていぎ(コンストラクターばん)
    Example(void);
    Example( int value );
    
    // (4): メンバー関数かんすう修飾しゅうしょくちがいによる多重たじゅう定義ていぎ
    int Function(void);
    int Function(void) const;
    
    // (1-3): 引数ひきすうかたちがいによる多重たじゅう定義ていぎ(メンバー関数かんすうばん)
    int Function( int value );

    // (5): もどかたちがいによる多重たじゅう定義ていぎ
    operator bool (void) const;
    operator int (void) const;
};

基本きほんてきには「(1)引数ひきすうかず」と「(2)修飾しゅうしょく」「(3)がた」がことなっていれば関数かんすうおな名前なまえけられるようになっている。また、大域たいいき関数かんすう可能かのう多重たじゅう定義ていぎはメンバー関数かんすうすべ可能かのうである。メンバー関数かんすうさらに「(4)修飾しゅうしょくちがい」による多重たじゅう定義ていぎ変換へんかん演算えんざんもちいたときかぎ可能かのうな「(5)もどかたちがい」による多重たじゅう定義ていぎ可能かのうになっている。JavaC#などC++以外いがい言語げんごでは(1)と(3)の範囲はんいにとどまっていることおおい。C++でとく特徴とくちょうてきなのは※1の省略しょうりゃくと※2のテンプレート関数かんすう多重たじゅう定義ていぎできるてんである。省略しょうりゃく引数ひきすうにとる関数かんすうはあらゆる引数ひきすうける関数かんすうである。引数ひきすうかたかず無視むしする反面はんめん関数かんすう内部ないぶでは一切いっさい引数ひきすう参照さんしょうすることができない。テンプレート関数かんすうはintとう明示めいじてきかたいた関数かんすうより選択せんたくされる優先ゆうせんひくく、省略しょうりゃくもちいた関数かんすうさらひくい。この特性とくせい利用りようしておなあつかいで処理しょりできるかたはテンプレート関数かんすう処理しょり特別とくべつあつかいが必要ひつようかたであれば明示めいじてきかたいた関数かんすう処理しょり引数ひきすうかずことな多重たじゅう定義ていぎした関数かんすうぐんでは対処たいしょしようがない引数ひきすう省略しょうりゃくもちいた関数かんすう使つかってなにもしないとう既定きてい処理しょりをさせるようにすることができる。


FORTRANによる多重たじゅう定義ていぎ[3]:

module Example

    implicit none
    
    ! Function0, Function1をFunctionとして定義ていぎ。FORTRANに予約よやくはなくFunctionは予約よやくではない。
    interface Function
        module procedure Function0, Function1
    end interface Function

contains

    function Function0( value1 ) result( value0 )
        !省略しょうりゃく
    end function Function0
    
    function Function1( value1, value2 ) result( value0 )
        !省略しょうりゃく
    end function Function1

end module Example

特徴とくちょうてきなのは関数かんすう定義ていぎとしては多重たじゅう定義ていぎみとめないものの方法ほうほうとして多重たじゅう定義ていぎみとめているてんである。名前なまえ定義ていぎ名前なまえ別物べつものであるため混乱こんらん原因げんいんとなるだけではあるがまったべつ名前なまえをつけること可能かのうになっている。

演算えんざん多重たじゅう定義ていぎ

[編集へんしゅう]

多重たじゅう定義ていぎ使つかった利用りようしゃ定義ていぎ演算えんざん一種いっしゅである。詳細しょうさい当該とうがい記事きじ参照さんしょうのこと。

オブジェクト指向しこう言語げんごにおいては数値すうちがたとオブジェクトをおな関数かんすう処理しょりするために必須ひっす機能きのうである(後述こうじゅつテンプレートと多重たじゅう定義ていぎ参照さんしょう)。

テンプレートと多重たじゅう定義ていぎ

[編集へんしゅう]

C++のよう多重たじゅう定義ていぎとテンプレートを使用しよう可能かのう言語げんごでは、両方りょうほう機能きのうわせることにより静的せいてきたい実現じつげんすることができる。また、PostgreSQLのストアドプロシージャーのようなテンプレートをそなえていない言語げんごでも同様どうようたい実現じつげんできる場合ばあいがある。

以下いかれいしめす。

#include <cmath>
#include <cstdlib>

// 引数ひきすうvalueの符号ふごうおうじて、-1, 1または0をかえ関数かんすう
// ※absを使用しようしているため、引数ひきすう指定していできる範囲はんいはこの関数かんすう引数ひきすうおながた(はんへんかた)をとるabsの仕様しよう依存いぞんする。
template<class Type> Type Sign( Type const &value )
{
    return Type() == value ? Type() : abs( value ) / value; // 除算じょざん演算えんざんおよび、abs関数かんすう実体じったいはSignの引数ひきすうによってわる
}

int main(void)
{
    double value = -2;
    std::valarray<double> array( 2 );

    double value_sign = Sign( value ); // doubleがたの-1, 1または0がかえ
    std::valarray<double> array_sign = Sign( array ); // -1, 1または0をふくむvalarrayのかえ

    return EXIT_SUCCESS;
}

このれいではSign関数かんすう内部ないぶ演算えんざんとabs関数かんすうがSign関数かんすう引数ひきすう指定していしたによって変化へんかする。なお上記じょうき関数かんすうテンプレートは複素数ふくそすうかた std::complexたいして適用てきようすることもできる。その場合ばあい結果けっか正規せいきされた複素数ふくそすう正規せいきベクトル)となり、符号ふごう関数かんすう複素数ふくそすうへの拡張かくちょう一致いっちする。

このようにテンプレートと多重たじゅう定義ていぎそなえる言語げんごでは、多重たじゅう定義ていぎでオーバーライドを代用だいようすることができる。

多重たじゅう定義ていぎによるたいは、コンパイルにしか実現じつげんできないという問題もんだいがあるものの単純たんじゅんなオーバーライドでは実現じつげんしづらい各種かくしゅ柔軟じゅうなんせいそなえている。

まず、メンバー関数かんすうだけでなく大域たいいきスコープの関数かんすうをクラスのインターフェースの一部いちぶとして做すこと出来できるようになる。これにより、たん手続てつづがた要素ようそでしかかった大域たいいきスコープの関数かんすうをオブジェクト指向しこう機能きのういち要素ようそとしてれること出来できる。そして、大域たいいきスコープの関数かんすうがインターフェースとして機能きのうはじめることによりクラスだけでなく、intやdoubleがたといったメンバー関数かんすうてないかたにもオブジェクト指向しこう恩恵おんけいられるようになるのである。

つぎに、大域たいいきスコープの関数かんすう直接ちょくせつクラスに所属しょぞくしないという特性とくせいによりメンバー関数かんすうより柔軟じゅうなん拡張かくちょうせいこと出来できる。たとえば、外部がいぶのライブラリーのあるクラスにメンバー関数かんすう追加ついかすることは、外部がいぶのライブラリーにくわえなければいけないため事実じじつじょう無理むりである。それにたい大域たいいきスコープの関数かんすう追加ついかする場合ばあいは、外部がいぶのライブラリーにくわえる必要ひつようがなく容易よういである。また、関数かんすうをテンプレートで実装じっそうすれば複数ふくすうのクラスを横断おうだんてき拡張かくちょうできる。さきにテンプレート関数かんすう存在そんざいする場合ばあいや、拡張かくちょう対象たいしょうのクラスのしんクラスにたいする関数かんすう存在そんざいする場合ばあいあらたに、より具体ぐたいてきかた引数ひきすう関数かんすう追加ついかすることで静的せいてきなオーバーライドが可能かのうとなる。

つぎに、単一たんいつディスパッチでは不可能ふかのう多重たじゅうディスパッチ模倣もほうできるというてんがある。これにより、たとえば矩形くけい描画びょうがしようとするさい描画びょうがさきデバイスが矩形くけい描画びょうが対応たいおうしていれば、デバイスに直接ちょくせつ矩形くけい情報じょうほうおくり、描画びょうがさきデバイスが矩形くけい描画びょうが対応たいおうしていなければ、パスや線分せんぶんとうその機能きのう使つかって矩形くけい描画びょうがするといった処理しょり自然しぜんかたち記述きじゅつ可能かのうとなる。

なお大域たいいきスコープと記述きじゅつしているが名前なまえ空間くうかんなかにあってもつぎれいのようにたいせい実現じつげん可能かのうである[2]。この仕組しくみをじつ引数ひきすう依存いぞん名前なまえ探索たんさくという。

#include <cstdlib>

namespace Graphics
{
        class Line { /* 省略しょうりゃく */ };
        class Ellipse { /* 省略しょうりゃく */ };

        class Square
        {
                /* 省略しょうりゃく */
                Line At( size_t index ) const; // 四角形しかっけいあたりかえ関数かんすう
                /* 省略しょうりゃく */
        };

        void Draw( ... )
        {
        }

        // 四角形しかっけい描画びょうがする
        template<class Type> void Draw( Type &device, Square const &shape )
        {
                Draw( device, shape.At( 0 ) );
                Draw( device, shape.At( 1 ) );
                Draw( device, shape.At( 2 ) );
                Draw( device, shape.At( 3 ) );
        }
}

namespace RasterDevice
{
        struct Device { /* 省略しょうりゃく */ };
        // Raster形式けいしきよう線分せんぶん描画びょうがする
        void Draw( Device &device, Graphics::Line const &shape );
}

namespace VectorDevice
{
        struct Device { /* 省略しょうりゃく */ };
        // Vector形式けいしきよう線分せんぶん描画びょうがする
        void Draw( Device &device, Graphics::Line const &shape );
}

namespace DisplayDevice
{
        struct Device { /* 省略しょうりゃく */ };
        // 表示ひょうじ装置そうちよう線分せんぶん描画びょうがする
        void Draw( Device &device, Graphics::Line const &shape );
}

int main()
{
        RasterDevice::Device device0;
        VectorDevice::Device device1;
        DisplayDevice::Device device2;

        Draw( device0, Graphics::Square( 0, 0, 1, 1 ) ); // 内部ないぶでRasterDevice::Draw( Device &, Graphics::Line const & )を
        Draw( device1, Graphics::Square( 0, 0, 1, 1 ) ); // 内部ないぶでVectorDevice::Draw( Device &, Graphics::Line const & )を
        Draw( device1, Graphics::Square( 0, 0, 1, 1 ) ); // 内部ないぶでDisplayDevice::Draw( Device &, Graphics::Line const & )を

        Draw( device0, Graphics::Ellipse( 0, 0, 1, 1 ) ); // Graphics::Draw( ... )を
        Draw( device1, Graphics::Ellipse( 0, 0, 1, 1 ) ); // Graphics::Draw( ... )を
        Draw( device2, Graphics::Ellipse( 0, 0, 1, 1 ) ); // Graphics::Draw( ... )を

        return EXIT_SUCCESS;
}

多重たじゅう定義ていぎ濫用らんよう弊害へいがい

[編集へんしゅう]

たとえば、C++ においてなんらかの数値すうちがたたとえば有理数ゆうりすうがたのためのクラスを定義ていぎするとして、整数せいすうがた引数ひきすうにとるabs関数かんすう絶対ぜったいかえすにもかかわらず有理数ゆうりすうがたをとるabs関数かんすうまったちが意味いみ定義ていぎすると関数かんすうテンプレートなどでおな処理しょり共有きょうゆうできないばかりでなく混乱こんらんまねく。互換ごかんせい多重たじゅう定義ていぎけるべきである。

曖昧あいまいかた言語げんご

[編集へんしゅう]

PerlPHPのような曖昧あいまいかた言語げんごでは、関数かんすう多重たじゅう定義ていぎができない、あるいは制限せいげんされていることがある。そのときは関数かんすう先頭せんとう引数ひきすうかた判定はんていする条件じょうけん分岐ぶんき対応たいおうする。

また、PHPには「オーバーロード」という機能きのう存在そんざいするが、これはプロパティやメソッドを動的どうてき作成さくせいするための機能きのうであり、おおくのオブジェクト指向しこう言語げんごとはことなる意味いみもちいられている。[4]

脚注きゃくちゅう

[編集へんしゅう]

注釈ちゅうしゃく

[編集へんしゅう]
  1. ^ えい: function overloading
  2. ^ えい: operator overloading
  3. ^ えい: method overloding
  4. ^ えい: method overriding
  5. ^ えい: implicit type conversion
  6. ^ えい: type coercion
  7. ^ えい: inheritance
  8. ^ えい: inclusion
  9. ^ えい: generic type
  10. ^ えい: parametric type
  11. ^ えい: polymorphism

出典しゅってん

[編集へんしゅう]
  1. ^ std::hypot - cppreference.com
  2. ^ a b http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf
  3. ^ http://www.j3-fortran.org/doc/year/10/10-007.pdf
  4. ^ オーバーロード”. 言語げんごリファレンス. The PHP Group. 2014ねん4がつ16にち閲覧えつらん

関連かんれん項目こうもく

[編集へんしゅう]