5.アレイを使用する際の一般的な落とし穴。
5.1落とし穴:タイプを信頼する-安全でないリンク。
OK、グローバル(変換ユニットの外部からアクセスできる名前空間スコープ変数)はEvil™であると言われたか、自分自身を発見しました。しかし、あなたは彼らがどれほど本当にEvil™であるか知っていましたか?2つのファイル[main.cpp]と[numbers.cpp]で構成される以下のプログラムについて考えてみます。
// [main.cpp]
#include <iostream>
extern int* numbers;
int main()
{
using namespace std;
for( int i = 0; i < 42; ++i )
{
cout << (i > 0? ", " : "") << numbers[i];
}
cout << endl;
}
// [numbers.cpp]
int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
Windows 7では、これはコンパイルされ、MinGW g++4.4.1とVisualC++10.0の両方と正常にリンクします。
タイプが一致しないため、実行するとプログラムがクラッシュします。

正式な説明:プログラムには未定義動作(UB)があるため、クラッシュする代わりに、ハングするか、何もしないか、米国、ロシア、インドの大統領に脅迫的な電子メールを送信する可能性があります。中国とスイス、そして鼻のデーモンをあなたの鼻から飛ばさせます。
実際の説明:main.cpp
配列内はポインタとして扱われ、配列と同じアドレスに配置されます。32ビット実行可能ファイルの場合、これは
int
、配列の最初の値がポインターとして扱われることを意味します。main.cpp
つまり、
変数numbers
にが含まれている、または含まれているように見える(int*)1
。これにより、プログラムはアドレス空間の最下部にあるメモリにアクセスします。これは従来は予約されており、トラップの原因になります。結果:クラッシュします。
C++11§3.5/10によると、宣言の互換性のある型の要件について、コンパイラはこのエラーを診断しない権利を完全に有しています。
[N3290§3.5/10]
タイプIDに関するこのルールの違反は、診断を必要としません。
同じ段落で、許可されるバリエーションについて詳しく説明しています。
…配列オブジェクトの宣言では、バインドされた主要な配列の有無によって異なる配列タイプを指定できます(8.3.4)。
この許可されたバリエーションには、名前を1つの変換ユニットで配列として宣言したり、別の変換ユニットでポインターとして宣言したりすることは含まれていません。
5.2落とし穴:時期尚早の最適化を行う(memset
&友達)。
まだ書かれていません
5.3落とし穴:Cイディオムを使用して要素の数を取得します。
深いCの経験があれば、書くのは自然なことです…
#define N_ITEMS( array ) (sizeof( array )/sizeof( array[0] ))
array
必要に応じて最初の要素へのポインタに減衰するため、式は。sizeof(a)/sizeof(a[0])
と書くこともできます
sizeof(a)/sizeof(*a)
。それは同じことを意味し、それがどのように書かれていても、それは配列の数要素を見つけるためのCイディオムです。
主な落とし穴:Cイディオムはタイプセーフではありません。たとえば、コード…
#include <stdio.h>
#define N_ITEMS( array ) (sizeof( array )/sizeof( *array ))
void display( int const a[7] )
{
int const n = N_ITEMS( a ); // Oops.
printf( "%d elements.\n", n );
}
int main()
{
int const moohaha[] = {1, 2, 3, 4, 5, 6, 7};
printf( "%d elements, calling display...\n", N_ITEMS( moohaha ) );
display( moohaha );
}
にポインタを渡すN_ITEMS
ため、間違った結果が生成される可能性があります。Windows 7で32ビットの実行可能ファイルとしてコンパイルされ、生成されます…
7つの要素、ディスプレイの呼び出し...
1つの要素。
- コンパイラは
int const a[7]
ただに書き直しint const a[]
ます。
- コンパイラはに書き換え
int const a[]
ますint const* a
。
N_ITEMS
したがって、ポインタを使用して呼び出されます。
- 32ビット実行可能ファイル
sizeof(array)
(ポインタのサイズ)の場合、4になります。
sizeof(*array)
はと同等sizeof(int)
であり、32ビット実行可能ファイルの場合も4です。
実行時にこのエラーを検出するために、次のことができます…
#include <assert.h>
#include <typeinfo>
#define N_ITEMS( array ) ( \
assert(( \
"N_ITEMS requires an actual array as argument", \
typeid( array ) != typeid( &*array ) \
)), \
sizeof( array )/sizeof( *array ) \
)
7つの要素、displayを呼び出しています...
アサーションに失敗しました:( "N_ITEMSには引数として実際の配列が必要です"、typeid(a)!= typeid(&* a))、ファイルruntime_detect ion.cpp、16行目
このアプリケーションは、ランタイムに異常な方法でそれを終了するように要求しました。
詳細については、アプリケーションのサポートチームにお問い合わせください。
ランタイムエラー検出は、検出しないよりも優れていますが、プロセッサ時間を少し浪費し、おそらくプログラマーの時間を大幅に浪費します。コンパイル時の検出が優れています!また、C ++ 98でローカル型の配列をサポートしないことに満足している場合は、次のことができます。
#include <stddef.h>
typedef ptrdiff_t Size;
template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }
#define N_ITEMS( array ) n_items( array )
この定義をg++を使用して最初の完全なプログラムに置き換えてコンパイルすると、次のようになります…
M:\ count> g ++ compile_time_detection.cpp
compile_time_detection.cpp:関数内'void display(const int *)':
compile_time_detection.cpp:14:エラー:'n_items(const int *&)'の呼び出しに一致する関数がありません
M:\ count> _
仕組み:配列はへの参照によって渡されるn_items
ため、最初の要素へのポインターに減衰せず、関数は型で指定された要素の数を返すことができます。
C ++ 11を使用すると、これをローカルタイプの配列にも使用できます。これは、配列の要素数を見つけるためのタイプセーフな
C++イディオムです。
5.4 C++11およびC++14の落とし穴:constexpr
配列サイズ関数を使用します。
C ++ 11以降では当然ですが、危険です!、C++03関数を置き換える
typedef ptrdiff_t Size;
template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }
と
using Size = ptrdiff_t;
template< class Type, Size n >
constexpr auto n_items( Type (&)[n] ) -> Size { return n; }
ここで重要な変更は、の使用ですconstexpr
。これにより、この関数はコンパイル時定数を生成できます。
たとえば、C ++ 03関数とは対照的に、このようなコンパイル時定数を使用して、別の配列と同じサイズの配列を宣言できます。
// Example 1
void foo()
{
int const x[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
constexpr Size n = n_items( x );
int y[n] = {};
// Using y here.
}
constexpr
ただし、バージョンを使用してこのコードを検討してください。
// Example 2
template< class Collection >
void foo( Collection const& c )
{
constexpr int n = n_items( c ); // Not in C++14!
// Use c here
}
auto main() -> int
{
int x[42];
foo( x );
}
落とし穴:2015年7月の時点で、上記はMinGW-64 5.1.0で
コンパイルされ、gcc.godbolt.org /-pedantic-errors
のオンラインコンパイラでテストされています。また、 clang3.0およびclang3.2でテストされていますが、clang 3.3、3.4ではテストされていません。 1、3.5.0、3.5.1、3.6(rc1)または3.7(実験的)。また、Windowsプラットフォームにとって重要なのは、Visual C ++ 2015ではコンパイルされないことです。理由は、constexpr
式での参照の使用に関するC ++ 11 / C++14ステートメントです。
C ++ 11 C ++ 14 $ 5.19 /29
番目のダッシュ
条件式 e
は、抽象マシン(1.9)の規則に従って、の評価が次の式のいずれかを評価しない限り、コア定数式です。
⋮ e
- 参照に先行する初期化があり、いずれかがない限り、参照型の変数またはデータメンバーを参照
するid式
- 定数式で初期化されるか、
- これは、eの評価内で存続期間が開始されたオブジェクトの非静的データメンバーです。
より冗長なものをいつでも書くことができます
// Example 3 -- limited
using Size = ptrdiff_t;
template< class Collection >
void foo( Collection const& c )
{
constexpr Size n = std::extent< decltype( c ) >::value;
// Use c here
}
Collection
…しかし、が生の配列でない場合、これは失敗します。
非配列になる可能性のあるコレクションを処理するには、
n_items
関数のオーバーロード性が必要ですが、コンパイル時の使用には、配列サイズのコンパイル時の表現が必要です。また、C++11およびC++14でも正常に機能する従来のC++03ソリューションは、関数に結果を値としてではなく、関数の結果タイプを介して報告させることです。たとえば、次のようになります。
// Example 4 - OK (not ideal, but portable and safe)
#include <array>
#include <stddef.h>
using Size = ptrdiff_t;
template< Size n >
struct Size_carrier
{
char sizer[n];
};
template< class Type, Size n >
auto static_n_items( Type (&)[n] )
-> Size_carrier<n>;
// No implementation, is used only at compile time.
template< class Type, size_t n > // size_t for g++
auto static_n_items( std::array<Type, n> const& )
-> Size_carrier<n>;
// No implementation, is used only at compile time.
#define STATIC_N_ITEMS( c ) \
static_cast<Size>( sizeof( static_n_items( c ).sizer ) )
template< class Collection >
void foo( Collection const& c )
{
constexpr Size n = STATIC_N_ITEMS( c );
// Use c here
(void) c;
}
auto main() -> int
{
int x[42];
std::array<int, 43> y;
foo( x );
foo( y );
}
リターンタイプの選択について:このコードは、結果が値として直接表されるため、元の問題を再導入する
ため、static_n_items
使用しません。クラスの代わりに、関数に配列への参照を直接返すようにすることができます。ただし、誰もがその構文に精通しているわけではありません。std::integral_constant
std::integral_constant
constexpr
Size_carrier
命名について:constexpr
-invalid-due-to-reference問題に対するこの解決策の一部は、コンパイル時定数の選択を明示的にすることです。
うまくいけば、oops-there-was-a-reference-involved-in-your- constexpr
issueはC ++ 17で修正されますが、それまでは、STATIC_N_ITEMS
上記のようなマクロは、たとえばclangおよびVisual C ++コンパイラーに対して、型を保持して移植性をもたらします。安全性。
関連:マクロはスコープを尊重しないため、名前の衝突を避けるために、名前のプレフィックスを使用することをお勧めしますMYLIB_STATIC_N_ITEMS
。