yohhoyの日記

技術的メモをしていきたい日記

広い契約(Wide Contracts)とnoexcept指定

プログラミング言語C++のライブラリ設計における、関数へのnoexcept指定と外部仕様の関係についてメモ。

2021-07-07追記:本記事が説明するC++標準ライブラリの設計ガイドラインは、当時の提案者名から "The Lakos Rule" と呼ぶらしい。記事 The Lakos Rule も参照のこと。

2021-07-07追記:2020年2月会合で提案文書 P1656 "Throws: Nothing" should be noexcept が採択され*1、後継の (PDF)P2148R0 Library Evolution Design Guidelines ではC++2b(C++23)以降に向けて「狭い契約(Narrow Contracts)の関数でもLWG合意が得られたものは無条件noexcept指定を行う」とライブラリ設計ガイドラインが改定された。

  • 2023-12-27追記:新しいライブラリ設計ガイドラインP1656に対しては (PDF)P2831R0, (PDF)P2861R0 などの反対意見が提出され、(PDF)P2979R0によれば2023年6月会合にて「The Lakos Rule は維持する」と合意したとのこと*2(thanks to onihusube9さん

関数の契約

C++11標準ライブラリの策定過程において、関数がもつ契約(Contracts)という概念に基づき、下記2つの用語が定義された。

広い契約(Wide Contracts)
関数が未定義動作(undefined behavior)を引き起こす可能性がない。関数は事前条件を規定しない、つまり追加の実行時制約・関数の引数・オブジェクトの状態・外部のグローバルな状態に対して前提を設けない。
例:std::vector<T>::begin()は、対象オブジェクトが保持する要素数によらずイテレータを返す。std::vector<T>::at(size_type)は、引数が有効インデクス値であれば要素への参照を返し、範囲外であれば例外std::out_of_rangeを送出する。いずれの動作もメンバ関数の外部仕様通りであり、未定義動作が生じることはない。
狭い契約(Narrow Contracts)
“広い契約” 以外の全て。ドキュメント化された契約、つまり事前条件に違反する関数呼び出しは未定義動作を引き起こす。
例:std::vector<T>::front()は、対象オブジェクトが空(empty)の場合は未定義動作。std::vector<T>::operator[](size_type)は、引数インデクス値が有効範囲外の場合は未定義動作。

C++標準ライブラリ設計のガイドライン

C++11の標準ライブラリ設計では、N3279にて下記のガイドラインが採用された。経緯および根拠はN3248参照のこと。

  • ライブラリ提供のデストラクタは、決して例外を送出しない。暗黙のnoexcept指定を用いるべき。*3
  • 例外を送出せず、かつ “広い契約” をもつ関数は、無条件noexcept指定を用いるべき。*4
  • swap関数/ムーブコンストラクタ/ムーブ代入演算子が条件付きで広い契約を持つ場合、該当関数もまた条件付きのnoexcept指定を行うべき。その他の関数では条件付きnoexcept指定を使うべきでない。
    • 例:関数テンプレートstd::swap(T&,T&)では、パラメータT型のムーブ操作が例外を送出しない/送出するという条件に応じて、swap関数自体のnoexcept(true/false)指定が決まる。
  • Cライブラリ互換が目的の関数では、無条件noexcept指定を用いてもよい。

ガイドラインにより、C++標準ライブラリでは関数仕様に "Throws: Nothing." とあってもnoexcept指定されないケースが存在する。つまり当該関数は “狭い契約” を持つため、要件(Requires)に違反した関数呼び出しは未定義動作を引き起こす。ガイドラインの適用により、未定義動作の一つとして “例外送出” という振る舞いを許容するとも解釈できる。

関連URL

*1:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4852.pdf , http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4870.pdf

*2:P2979R0, §5: "(snip), the entire joint session of LWG and LEWG at Varna agreed, with seemingly overwhelming consensus (i.e., by verbal acclamation; no poll was taken), that the Lakos Rule is an essential design policy and, if anything, not restrictive enough."

*3:デストラクタでは暗黙にnoexcept指定が行われる。つまり noexcept(false) を明示しない限り、デストラクタから例外送出することはできない。

*4:関数へのnoexcept指定には 1) noexceptキーワードのみ、2) noexcept(真偽値) の2種類が存在する。無条件noexcept指定は前者1)を意味する。