LoginSignup
11
12

More than 5 years have passed since last update.

知らなかったC++の仕様

Last updated at Posted at 2018-02-08

最近、C++ を少し真面目に勉強し始めました。
もう5年以上 C++ を書いていますが、自分がいかに無知だったかを思い知らされました。
ということで、自分が知って驚いた C++ の仕様をまとめます。
C++11 現在の仕様(たぶん)なので、C++14 とか C++17 では変わっている可能性もあります。

戻り値を返さなくても良い関数

戻り値がある関数は、必ず値を返さなければなりません。
しかし、1つだけ例外的に戻り値を返さなくても良い関数があります。
main 関数です。

main 関数の戻り値は int ですが、return 文を書かなくてもコンパイルは通ります。
return 文を省略した場合、return 0; と見なされます。

配列の添え字

一般的に、配列の添え字は a[1] のように [] の中に配列要素のインデックスを指定します。
これで指定した要素にアクセスできます。

int a[3] = { 0, 1, 2 };
int b = a[1];

しかし、x[y] の x が配列のポインター、y がインデックスという規格はありません。
規格上は、x か y の、どちらか一方が配列のポインターで、もう一方がインデックスであれば良いのです。
つまり a[1] ではなく 1[a] と書いてもコンパイルは通ります。

1[a] という書き方は一般的じゃないし、直感的に意味が分かりにくいので、もし見かけたら文句を言うと思いますけど、規格上は問題ありません。

char != signed char

int == signed int です。
short == signed short です。
long == signed long です。
が、char == signed char ではありません。
char == unsigned char でもありません。
char が signed になるか、unsigned になるかは実装依存です(一般的には signed だと思いますが)。

signed で実装されているからと言っても、char == signed char になるわけではありません。
charsigned charunsigned char はそれぞれ別物です。

以下はコンパイルエラーになります。
なぜなら shortsigned short は同じ型なので「toInt の再定義」になるからです。

int toInt( short s ) {
    return s;
}
int toInt( signed short s ) {
    return s;
}

以下はコンパイルが通ります。
charsigned charunsigned char も別の型なので再定義にはなりません。

int toInt( char c ) {
    return c;
}
int toInt( signed char c ) {
    return c;
}
int toInt( unsigned char c ) {
    return c;
}

整数プロモーション

charshort は、演算時に自動的にプロモーションが行われます。

プロモーションとは、int よりもサイズの小さい型から int への変換のことです(boolは除く)。
int 以上の型からそれ以上に型への変換、サイズの大きい型から小さい型への変換は「型変換」と呼ぶらしいです。
なお、unsigned charunsigned short の場合は unsigned int に変換(プロモーション)されます。

以下の b の型は int になります。

short a = 0;
auto b = a + a;

また、

char a = 0;
printf("%lu\n", sizeof(a));
printf("%lu\n", sizeof(-a));
printf("%lu\n", sizeof(a + a));

の結果は

1
4
4

になります。

関数の実引数の評価順

関数の実引数に式を指定する場合、どの順番で評価されるかは実装依存です。
もちろん、関数が呼ばれる前に全ての式が評価されることは保証されています。

以下を実行した結果、
clang 4.0 では 123123123 でしたが
gcc 6.3 と msvc++ 2015 では 321321321 でした。
clang は実引数の先頭から、gcc と msvc++ は末尾から評価しているようです。

std::string& func1( std::string& s ) {
    s += "1";
    return s;
}

std::string& func2( std::string& s ) {
    s += "2";
    return s;
}

std::string& func3( std::string& s ) {
    s += "3";
    return s;
}

void outputString( std::string& s1, std::string& s2, std::string& s3 ) {
    std::cout << s1 << s2 << s3 << std::endl;
}

int main(void){
    std::string s = "";
    outputString( func1( s ), func2( s ), func3( s ) );
}

[おまけ] free や delete は NULL チェック不要

これは2年ぐらい前まで知りませんでした。
意外と知らない人が多そう。

delete や free は NULL を渡しても問題ありません。

よく

if ( p ) {
    delete p;
}

// or

if ( p ) {
    free( p );
}

という実装を見かけますが、NULLチェックは不要です。
以下の p が nullptr や NULL でも何も起きません(クラッシュしません)。

delete p;

// or

free( p );

私もそうでしたが、なんとなく危険な感じがして、盲目的に NULL チェックしている人が多いのではないでしょうか。

あとがき

「知らなかった」は言い換えると「知らなくても困らなかった」とも言えます。
運良く(?)、それを知らないと困るような仕事をしてこなかったとも言えます。

配列の添字とかは知らなくても何も困らないと思いますが、char の話とか整数プロモーションの話は知っておいた方が良さそうですね。

意外と知らない人も多いと思うので、周りのC++プログラマーに話したらドヤれるかもしれませんw

11
12
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
12