Menu
Coddy logo textTech

コンパイル時と実行時の多相性

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 56/104。

ポリモーフィズムとは「多くの形態」を意味し、オブジェクトを一様に扱いながら異なる動作をさせることができる、OOPの中核的な概念です。C++は、プログラムの実行の異なる段階でそれぞれ解決される、2つの異なる種類のポリモーフィズムをサポートしています。

Compile-time ポリモーフィズム(静的ポリモーフィズムとも呼ばれます)は、プログラムの実行前にコンパイラによって解決されます。コンパイラは、関数シグネチャに基づいて、どの関数を呼び出すかを正確に決定します。これには、関数のオーバーロードとテンプレートが含まれます。

void print(int x) { std::cout << "Integer: " << x << std::endl; }
void print(double x) { std::cout << "Double: " << x << std::endl; }

print(5);      // コンパイラは print(int) を選択します
print(3.14);   // コンパイラは print(double) を選択します

実行時ポリモーフィズム(動的ポリモーフィズムとも呼ばれます)は、プログラムの実行中に解決されます。どの関数を呼び出すかは、ポインター型や参照型ではなく、実際のオブジェクト型に依存します。これは仮想関数によって実現されます。

class Shape {
public:
    virtual void draw() { std::cout << "Drawing shape" << std::endl; }
};

class Circle : public Shape {
public:
    void draw() override { std::cout << "Drawing circle" << std::endl; }
};

Shape* s = new Circle();
s->draw();  // 実行時に決定: "Drawing circle"

重要なトレードオフは、コンパイル時ポリモーフィズムではコンパイル中に判断が行われるため実行時のオーバーヘッドがゼロである一方、実行時ポリモーフィズムではわずかなコスト(vtable の検索)が発生するものの、実行時まで型がわからないオブジェクトを扱う際の柔軟性が高まることです。

challenge icon

チャレンジ

簡単

2種類のポリモーフィズムを並べて示す電卓システムを構築しましょう。function overloading によって compile-time polymorphism が異なる入力型を処理し、runtime polymorphism によって異なる calculation 戦略を dynamically 切り替えられるシステムを作成します。

コードを3つのファイルに分けて構成します。

  • Calculator.h:任意の calculation 戦略を表す base Calculator class を定義します。
    • int aint b を受け取り、int を返して、Base calculation: <a> ? <b> を出力する virtual calculate(int a, int b) method(0を返す)
    • virtual destructor
  • Operations.h:calculation の動作を override する2つの derived calculator class を定義します。
    • Addercalculate() を override して Adding: <a> + <b> を出力し、合計を返す
    • Multipliercalculate() を override して Multiplying: <a> * <b> を出力し、積を返す
    どちらの class も override keyword を使用します。
  • main.cpp:両方のポリモーフィズムの型を示すシステムを作成します。2つの integer inputs を読み込みます(それぞれ別の行に入力)。

    まず、3つの overloaded display() functions を作成して、compile-time polymorphism を示します。

    • display(int x) は次を出力します:Integer value: <x>
    • display(double x) は次を出力します:Double value: <x>
    • display(const std::string& x) は次を出力します:String value: <x>

    次に、base CalculatorAdderMultiplier を格納する Calculator* pointers の array を作成して、runtime polymorphism を示します。各要素を Loop して、それぞれに入力値を使って calculate() を call し、各 calculation の後に結果を出力します。

    出力を次のように構成します。

    === Compile-Time Polymorphism ===
    <display outputs for int, double, string>
    
    === Runtime Polymorphism ===
    <calculate outputs with results>

    compile-time セクションでは、最初の入力を integer として、次に .5 を加えた同じ値を double として、最後に文字列 "Result" として display() を call します。終了したら dynamically allocated calculators を Clean up してください。

例えば、入力が 103 の場合:

=== Compile-Time Polymorphism ===
Integer value: 10
Double value: 10.5
String value: Result

=== Runtime Polymorphism ===
Base calculation: 10 ? 3
Result: 0
Adding: 10 + 3
Result: 13
Multiplying: 10 * 3
Result: 30

コンパイラーが引数の型に基づいて適切な display() overload を選択する(compile-time の decision)一方で、適切な calculate() method は、vtable mechanism を通じて runtime に実際の object type によって決定されることに注目してください。

自分で試してみよう

#include <iostream>
#include <string>
#include "Calculator.h"
#include "Operations.h"

// TODO: オーバーロードされた display() 関数を3つ作成する:
// 1. display(int x) - prints "Integer value: <x>"
// 2. display(double x) - "Double value: <x>" を出力する
// 3. display(const std::string& x) - "String value: <x>" を出力する



int main() {
    // 2つの整数入力を読み取る
    int a, b;
    std::cin >> a;
    std::cin >> b;
    
    // === Compile-Time Polymorphism ===
    std::cout << "=== Compile-Time Polymorphism ===" << std::endl;
    // TODO: 次のように display() を呼び出す:
    // - 整数としての a
    // - double としての a(0.5 を加える)
    // - 文字列 "Result"
    
    
    std::cout << std::endl;
    
    // === Runtime Polymorphism ===
    std::cout << "=== Runtime Polymorphism ===" << std::endl;
    // TODO: Calculator* ポインタの配列を3要素で作成する:
    // - a base Calculator
    // - Adder
    // - Multiplier
    
    // TODO: 配列をループし、それぞれで calculate(a, b) を呼び出し、
    // そして各計算の後に "Result: <return_value>" を出力する
    
    
    // TODO: 動的に割り当てられたメモリを解放する
    
    
    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: C++オンラインコンパイラ