C++ コア ガイドライン:インターフェイス II

インターフェイスは、サービス プロバイダーとサービス コンシューマーの間の契約です。 C++ コア ガイドラインには、それらを正しくするための 20 のルールがあります。これは、「インターフェイスは、おそらくコード編成の最も重要な単一の側面である」ためです。

前回の投稿で、最初の 10 のルールについて書きました。今日は仕事を終えて、残りの 10 のルールについて書きます。

  • I.1:インターフェースを明示的にする
  • I.2:グローバル変数を避ける
  • I.3:シングルトンを避ける
  • I.4:インターフェースを正確かつ強く型付けする
  • I.5:状態の前提条件 (ある場合)
  • I.6:Expects() を優先 前提条件を表現するため
  • I.7:状態事後条件
  • I.8:Ensures() を優先 事後条件の表現用
  • I.9:インターフェースがテンプレートの場合、概念を使用してそのパラメーターを文書化します
  • I.10:例外を使用して、必要なタスクの実行に失敗したことを知らせる
  • I.11:生のポインターによって所有権を譲渡しない (T* )
  • I.12:null であってはならないポインターを not_null として宣言する
  • I.13:配列を単一のポインタとして渡さない
  • I.22:グローバル オブジェクトの複雑な初期化を避ける
  • I.23:関数の引数の数を少なく保つ
  • I.24:同じ型の関連性のないパラメータを隣接させない
  • I.25:クラス階層へのインターフェースとして抽象クラスを好む
  • I.26:クロスコンパイラ ABI が必要な場合は、C スタイルのサブセットを使用してください
  • I.27:安定したライブラリ ABI については、Pimpl イディオムを検討してください
  • I.30:ルール違反をカプセル化する

詳細に直接飛び込みましょう。

I.11:raw ポインターによって所有権を譲渡しない (T*)

このコードには概念上の問題があります。

X* compute(args) // don't
{
 X* res = new X{};
 // ...
 return res;
}

ポインター X を削除するのは誰ですか?所有権の問題に対処するには、少なくとも 3 つの代替手段があります。

  • 可能であれば値を返す
  • スマート ポインターを使用する
  • ガイドライン サポート ライブラリ (GSL) の所有者 を使用

I.12:必要なポインターを宣言するnot_null として null ではない

次の関数の長さの 3 つのバリエーションの意味上の違いは何ですか?

int length(const char* p); // it is not clear whether length(nullptr) is valid

int length(not_null<const char*> p); // better: we can assume that p cannot be nullptr

int length(const char* p); // we must assume that p can be nullptr

長さのバリエーション 2 と 3 の意図は明らかです。 2 番目のバリエーションは null 以外のポインターのみを受け入れ、3 番目のバージョンは nullptr を受け入れます。あなたはすでにそれを推測しているかもしれません。 GSL からの場合は not_null。


I.13:配列を渡さない単一のポインタとして

配列を単一のポインターとして渡すと、エラーが発生しやすくなります。

void copy_n(const T* p, T* q, int n); // copy from [p:p+n) to [q:q+n)

n が大きすぎるとどうなりますか?右:未定義の動作。 GSL は、スパンと呼ばれるソリューションを提供します。

void copy(span<const T> r, span<T> r2); // copy r to r2

スパンは引数の数を推測します。

I.22:グローバル オブジェクトの複雑な初期化を避ける

グローバル オブジェクトは、多くの楽しみを提供します。たとえば、それらが異なる翻訳単位にある場合、それらの初期化の順序は定義されていません。次のコード スニペットの動作は未定義です。

// file1.c

extern const X x;

const Y y = f(x); // read x; write y

// file2.c

extern const Y y;

const X x = g(y); // read y; write x


I.23:関数の引数の数を少なく保つ

単純なルールがあります。1 つの関数が 1 つのジョブを正確に実行する必要があります。その場合、関数の引数の数は自動的に少なくなり、関数は使いやすくなります。

正直なところ、標準テンプレート ライブラリの新しい並列アルゴリズム (std::transform_reduce など) は、このルールを破ることがよくあります。


I.24:同じタイプの隣接する無関係なパラメーターを避けるタイプ

次の copy_n 関数のコピー元とコピー先は?根拠のある推測はありますか?

void copy_n(T* p, T* q, int n); 

しばしばドキュメントを探す必要があります。


I.25:クラスへのインターフェースとして抽象クラスを優先する階層

もちろん、これはオブジェクト指向設計の明確で確立された規則です。ガイドラインは、この規則の 2 つの理由を示しています。

  • 抽象クラスは基本クラスよりも安定している可能性が高い
  • 状態メソッドと非抽象メソッドを持つ基本クラスは、派生クラスにより多くの制約を課します


I.26:もしクロスコンパイラ ABI が必要な場合は、C スタイルのサブセットを使用してください

ABI は für A を表します アプリケーション B inary

これは、C++ ガイドラインでは奇妙な規則です。その理由は、「異なるコンパイラは、クラス、例外処理、関数名、およびその他の実装の詳細に対して異なるバイナリ レイアウトを実装している」ためです。一部のプラットフォームでは、一般的な ABI が出現しています。単一のコンパイラを使用する場合は、完全な C++ インターフェイスに固執できます。この場合、コードを再コンパイルする必要があります。


I.27:安定したライブラリ ABI については、にきびイディオム

Pimpl は実装へのポインターを表し、ブリッジ パターンの C++ バリエーションです。非ポリモーフィック インターフェイスはその実装へのポインターを保持するため、実装を変更してもインターフェイスを再コンパイルする必要がないという考え方です。

以下は、C++ コア ガイドラインの例です:

interface (widget.h)
class widget {
 class impl;
 std::unique_ptr<impl> pimpl;
public:
 void draw(); // public API that will be forwarded to the implementation
 widget(int); // defined in the implementation file
 ~widget(); // defined in the implementation file, where impl is a complete type
 widget(widget&&) = default;
 widget(const widget&) = delete;
 widget& operator=(widget&&); // defined in the implementation file
 widget& operator=(const widget&) = delete;
};

implementation (widget.cpp)

class widget::impl {
 int n; // private data
public:
 void draw(const widget& w) { /* ... */ }
 impl(int n) : n(n) {}
};
void widget::draw() { pimpl->draw(*this); }
widget::widget(int n) : pimpl{std::make_unique<impl>(n)} {}
widget::~widget() = default;
widget& widget::operator=(widget&&) = default;

pimpl は、実装へのハンドルを保持するポインターです。

この C++ イディオムの詳細については、Herb Sutter による GOTW #100 記事を参照してください。 GotW は Guro of the Week の略です。


I.30:ルール違反をカプセル化する

さまざまな理由により、コードが醜い、安全でない、またはエラーが発生しやすい場合があります。コードを 1 か所に配置し、使いやすいインターフェイスでカプセル化します。それは抽象化と呼ばれ、時々やらなければならないことです。正直なところ、使用されている内部コードが安定していて、インターフェースが正しい方法でしか使用できない場合は、そのコードに問題はありません.

次は?

今回の投稿を含め、前回の投稿で、ガイドライン サポート ライブラリについてよく言及しました。では、インサイトを見てみましょう。それについては次の投稿で書きます。