コンテンツにスキップ

最終更新:2026-10-12

C++ ポリモーフィズム:仮想関数、CRTP と variant

C++ポリモーフィズムパフォーマンステスト:CRTP vs std::variant vs virtual

ポリモーフィズムはオブジェクト指向プログラミングの重要な概念で、単一のインターフェースで異なる型を表現できます。C++ にはいくつかのポリモーフィズム実現方法があり、本記事では3つのシナリオのポリモーフィズムを議論します:

  1. 仮想関数:C++ でポリモーフィズムを実現する伝統的な方法は仮想関数です。基底クラスと派生クラスを使って特定の実装を行います。
  2. std::variant:C++17 で導入された std::variant は、継承を必要としないポリモーフィズムを実現します。
  3. CRTP(Curiously Recurring Template Pattern):CRTP はやや特殊な技法で、テンプレートの奇妙な再帰パターンによってポリモーフィズムを実現します。

テストした組み合わせは以下の通りです:

  • crtp 単体
  • crtp + std::variant
  • virtual
  • std::variant + std::visit
  • std::variant + std::get_if
  • std::variant + std::holds_alternative

使用したコンパイラ:

  • gcc 13.2
  • clang17.0

完全なテストコードは**星球**(ナレッジプラネット)に置いてあります。ここでは重要なコードを示します。

class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;
};

class Circle : public Shape
class Rectangle : public Shape



// virtual
static void BM_VirtualFunction(benchmark::State& state) {
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.reserve(1000);

    for (int i = 0; i < 1000; ++i) {
        if (i % 2 == 0) {
            shapes.emplace_back(std::make_unique<Circle>(5.0));
        } else {
            shapes.emplace_back(std::make_unique<Rectangle>(4.0, 6.0));
        }
    }

    for (auto _ : state) {
        double totalArea = 0.0;
        for (const auto& shape : shapes) {
            totalArea += shape->area();
        }
        benchmark::DoNotOptimize(totalArea);
    }
}
BENCHMARK(BM_VirtualFunction);

static void BM_VariantVisit(benchmark::State& state) {
    std::vector<std::variant<Circle, Rectangle>> shapes;
    shapes.reserve(1000);

    for (int i = 0; i < 1000; ++i) {
        if (i % 2 == 0) {
            shapes.emplace_back(Circle(5.0));
        } else {
            shapes.emplace_back(Rectangle(4.0, 6.0));
        }
    }

    for (auto _ : state) {
        double totalArea = 0.0;
        for (const auto& s : shapes) {
            totalArea += std::visit([](const auto& shape) { return shape.area(); }, s);
        }
        benchmark::DoNotOptimize(totalArea);
    }
}
static void BM_CRTPVariant(benchmark::State& state) {
    std::vector<ShapeVariant> shapes;
    shapes.reserve(1000);

    for (int i = 0; i < 1000; ++i) {
        if (i % 2 == 0) {
            shapes.emplace_back(crtp_light::Circle(5.0));
        } else {
            shapes.emplace_back(crtp_light::Rectangle(4.0, 6.0));
        }
    }

    for (auto _ : state) {
        double totalArea = 0.0;
        for (const auto& s : shapes) {
            totalArea += std::visit([](const auto& shape) { return shape.area(); }, s);
        }
        benchmark::DoNotOptimize(totalArea);
    }
}
BENCHMARK(BM_CRTPVariant);

// ...

テスト結果1:gcc コンパイルでは、virtual と std::variant のパフォーマンス差は大きくありませんが、crtp との差は非常に大きいことがわかります。

gcc bm

テスト結果2:clang コンパイルでは全体傾向は gcc と同様で、crtp + std::variant のパフォーマンスだけが明らかに低下しています。これは std::visit を使っているためかもしれません。

clang bm

A Tour of C++ の書籍には次のように書かれています:

This is basically equivalent to a virtual function call, but potentially faster. As with all claims of performance, this ''potentially faster'' should be verified by measurements when performance is critical. For most uses, the difference in performance is insignificant.

今回のテスト結果とほぼ一致しています。

関連トピック

コメント