これは、 std :: stringパラメータに渡される0からどのように最善の方法で保護するかを補完する質問のようなものです 。基本的に、コードパスがstd::string
のchar*
コンストラクターをNULL
を使用して無条件に呼び出そうとする場合にコンパイラーに警告する方法があるかどうかを把握しようとしています。
実行時チェックはすべてうまく機能していますが、次のような場合に適しています。
std::string get_first(const std::string& foo) {
if (foo.empty()) return 0; // Or NULL, or nullptr
return foo.substr(0, 1);
}
そのコードパスが実行された場合にコードが失敗することが保証されているにもかかわらず、システムヘッダーには通常、ポインターがnullであってはならないという前提条件が付けられていますが、これはgcc
の下でコンパイルを渡しますclang
など、-std=c++11 -Wall -Wextra -pedantic -Werror
を使用した場合も同様です。 gcc
の0
の特定のケースを-Werror=zero-as-null-pointer-constant
でブロックできますが、これはNULL
/nullptr
には役立ちません。関連するが類似していない問題に取り組む。主な問題は、プログラマが0
、NULL
、またはnullptr
を使用してこの間違いを犯し、コードパスが実行されていない場合に気付かないということです。
コード全体でstd::string
を特別なサブクラスで置き換えるなどのナンセンスなしに、コードベース全体をカバーするこのチェックをコンパイル時間にすることは可能ですか?
すべてのコンパイラで機能するか、非常に制限された状況で一部の副作用があるか、または一部のみで機能するかによって、さまざまなアプローチがありますが、交換の範囲ははるかに広くなります。
使用時に文句を言うオーバーロードを追加します。 _[[deprecated]]
_ があります。C++ 11は、通常のシステムヘッダーのように抑制されない限り、即時の呼び出しサイトで文句を言うからです。
GCCとCLANGは、より適切なカスタム属性 __attribute__((error("message")))
を提供します。これは、関数が使用され、呼び出しサイトに名前を付けると、常にビルドを中断します。
Nullpointer-literalsである可能性のあるすべてのものを受け入れるオーバーロードを追加することの問題は、他のテンプレートのSFINAEを混乱させ、コードを破壊し、残念ながらnullpointerになってしまう_char*
_型の引数をキャッチできないことです。 :
_// added overload, common lines:
template<class X, class = typename
std::enable_if<std::is_arithmetic<X>() && !std::is_pointer<X>()>
// best on GCC / clang:
string(X) __attribute__((error("nullpointer")));
// best on others:
[[deprecated("UB")]] string(X) /* no definition, link breaks */;
_
望ましい代替策は、引数をnull以外としてマークし、コンパイラーのオプティマイザーにそれを理解させることです。あなたはそのような警告を求め、それに注意を払っていますよね?
これはGCCとCLANGのオプションにすぎませんが、追加のオーバーロードの不利な点を回避し、コンパイラーが理解できるすべてのケースをキャッチします。
_basic_string(const CharT* s,
size_type count,
const Allocator& alloc = Allocator()) __attribute__((nonnull));
basic_string(const CharT* s,
const Allocator& alloc = Allocator()) __attribute__((nonnull));
_
一般的に、GCC/clangに、コンパイル時に ___builtin_constant_p
_ を使用して式の値を決定できるかどうかを尋ねることができます。
コンパイル時には行われませんが、これを(少なくともほとんどの実装)std::string
が現在よりも適切に処理する文字列型を定義するのはかなり簡単です。
基本は、NULL
がint
型を持ち、nullptr
がnullptr_t
型を持つという事実です。そのため、これらの型をオーバーロードして、間違った型の引数を渡したときに「高速かつ大音量」で失敗するのはかなり簡単です。
#include <iostream>
#include <fstream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <numeric>
#include <string>
class test_string {
std::vector<char> data;
public:
test_string(nullptr_t) { throw std::runtime_error("Bad input data"); }
test_string(int) { throw std::runtime_error("Bad input data"); }
test_string(void *) { throw std::runtime_error("Bad input data"); }
test_string(char const *s) { size_t length = strlen(s); data.resize(length); std::copy_n(s, length, &data[0]); }
size_t length() const { return data.size(); }
friend std::ostream &operator<<(std::ostream &os, test_string const &s) {
std::copy(s.data.begin(), s.data.end(), std::ostream_iterator<char>(os));
return os;
}
};
int main() {
// These will all immediately throw if uncommmented:
//test_string x(NULL);
//test_string y(nullptr);
//test_string z(0);
// But this still works fine:
test_string a("This is a string");
std::cout << a;
}
std::string
でこれを行うのは、少し難しいかもしれません。問題は、std::string
には既にそのコンストラクタのかなりの数のオーバーロードがあることです。 nullptr_t
のオーバーロードは常に安全である必要があります(その型の唯一のものはnullptr
であり、明らかにそれを望まない)。
各charT const *
の代わりにint
を取得するオーバーロードがあると、さらに困難になる可能性があります。問題は、オーバーロードの一部が、(たとえば)ポインターの位置と長さを示すカウントに基づいてインテントをすでに区別していることです。 NULLで呼び出しをキャッチしていることを確認するためにかなり注意深く調べる必要がありますが、(たとえば)char
とsize_t
を必要とする既存の(正当な)使用を妨害していません。私はそれが不可能だとは確信していませんが、多少の注意が必要です(そして、まだ抜ける可能性のあるいくつかのケースを残しておく必要があるかもしれません)。
ただし、非常に実用的なものにするためには、おそらくstd::string
を変更する必要があります。これを別のクラスとして使用すると、かなり深刻な非実用性が(少なくとも私の意見では)発生します。