ブログ 約4分

C++20コンセプト:ジェネリクスパラダイムの欠点を解消する

Share this article
C++20コンセプト:ジェネリクスパラダイムの欠点を解消する

Bjarne Stroustrupが指摘しているように、「C++はマルチパラダイム言語」です。C++はさまざまなプログラミングスタイル、つまりパラダイムをサポートしており、オブジェクト指向プログラミングはその1つにすぎません。他には、構造化プログラミングやジェネリックプログラミングなどがあります。長年にわたり、Andrei Alexandrescu、Scott Meyers、Herb SutterなどのC++エキスパートは、しばしばモダンC++デザインと呼ばれるジェネリックプログラミングの使用を推進してきました。

Andrei AlexandrescuがモダンC++デザインについて述べていることは次のとおりです:

Modern C++ Design defines and systematically uses ジェネリックコンポーネント - highly flexible design artifacts that are mixable and matchable to obtain rich behaviors with a small, orthogonal body of code.

彼の考えでは、次の3つの側面が特に興味深いです:

  • Modern C++ Designでは、定義され、 体系的に使用される ジェネリックコンポーネント
  • 高度に 柔軟な 設計。
  • Obtain rich behaviors with a 小さく直交した コード本体で、豊かな振る舞いを実現する。

一方、OOPは非常に人気があります。継承とRTTIはC++アプリケーションを設計するための2つの強力な機構であり、多くの開発者はジェネリックプログラミングアプローチよりもこのパラダイムを好みます。

継承の一般的な定義は次のとおりです:

In object-oriented programming (OOP), 継承 is when an object or class is based on another object (prototypal inheritance) or class (class-based inheritance), using the same implementation (inheriting from an object or class) specifying implementation to maintain the same behavior (realizing an interface; inheriting behavior). It is a mechanism for code reuse and to allow independent extensions of the original software via public classes and interfaces.

多くのC++エキスパートは、継承と動的ポリモーフィズムの過剰使用を避けるよう推奨しています。では、継承の何が問題なのでしょうか?

簡単な答え: 非常に高い結合度

例として、税額計算クラスの実装を見てみましょう。

generics1

CTaxCalculatorは、ICalculatorを継承するクラスとのみ連携し、税額計算に役立つ可能性があっても、ICalculator以外のクラスは使用できません。計算機の実装クラスはICalculatorと強く結合し、この制限を回避するために追加のクラスやインターフェースを導入しない限り、別の種類のクラスを使用する方法はありません。たとえば、 アダプターパターン は、継承によって生じる強い結合を回避するための解決策です。考えてみてください。GoFデザインパターンの中には、継承によって導入される強い結合が原因の問題に対処するために存在するものがあります。

ジェネリックプログラミングによる解決

ジェネリックプログラミングでは、同じ税額計算機を次のように実装できます:

generics2

一方、CGenericTaxCalculatorクラスは、具体的な型を知る必要なく、税額を計算できる任意の型を使用して税額を計算します。 重要なのは、その型が実装しているメソッドであり、その型が属する特定のクラスではありません。 このため、ジェネリックプログラミングはより自然で柔軟です。実際、最初の実装は現実世界で言えば、開発者を探している企業が特定の学校の卒業生だけを採用し、必要なスキルを持っていても他の応募者をすべて拒否するようなものです。しかし、この柔軟性には代償があります。コードが理解しにくくなる可能性があるのです。OOPでは、ICalculatorの定義を見れば、この型に何が期待されているかを簡単に確認できます。一方、ジェネリックプログラミングアプローチでは、テンプレートパラメータに何が期待されているのか、どのメンバーが必要で、どの制約を満たさなければならないのかを正確に知るのが難しい場合があります。

C++20コンセプトによる解決

C++20コンセプトの簡単な説明は次のとおりです:

コンセプト are an extension to C++'s テンプレート, published as an ISO Technical Specification ISO/IEC TS 19217:2015.[1] They are named boolean predicates on template parameters, evaluated at compile time. A concept may be associated with a template (class template, function template, or member function of a class template), in which case it serves as a 制約: it limits the set of arguments that are accepted as template parameters.

コンセプト機能は何度か延期されてきました。良いニュースは、C++20にこの興味深い機能が含まれることです。

この興味深い ドキュメント で、コンセプト機能の背景にある動機を確認できます:

The intent of concepts is to model semantic categories (Number, Range, RegularFunction) rather than syntactic restrictions (HasPlus, Array). According to ISO C++コアガイドライン T.20, "The ability to specify a meaningful semantics is a defining characteristic of a true concept, as opposed to a syntactic constraint."

コンセプトを使用すれば、制約指定の問題に対処して、コードの可読性と保守性を高められます。確かに、Boostは長年にわたりコンセプトの実装を提供してきましたが、言語に追加することで、C++はより強力で独自性の高いものになります。

ジェネリックプログラミングのもう1つのよく知られた欠点は、エラーメッセージです。コンパイラがエラーを報告する理由を簡単に理解できず、コードが期待どおりに動作しない正確な理由を突き止めようとして時間を無駄にすることがあります。

幸い、コンセプト機能はエラーメッセージも改善します。詳しくは こちら

概要

OOPアプローチを選択した際に継承を過剰に使用すると、クラス間に高い結合が生じ、この問題を解決するためだけにクラスを追加しなければならなくなります。ジェネリックプログラミングアプローチを採用する場合には、このようなことはありません。そして幸いなことに、可読性が高く保守しやすい低結合コードを実装するために欠けていた要素が、まもなく言語の一部になります。

Share this article