syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

2023年最後の映画の時間

tvkの「映画の時間」、たまに観るんですがラインナップが渋いですね。去年(2023年)の年始はシティーハンターの劇場版「愛と宿命のマグナム」でした。2023年の末は「犬神家の一族」と「らんま1/2」と「スラムダンク」。どれも今まで観る機会がなかったんですが最高に楽しめました。
2023年は仕事が忙しすぎて劇場で映画を観る精神的余裕がまったくありませんでした。『Winny』しか観ていない気がする。2023年は物価が上がりまくった年で、便乗でチケット代も値上がりしたし、もう映画なんて気軽に行けるようなものではないですね*1ガンダムSEEDも最初は観ようかと思っていたんですが、カガリのキャスト交代が発表された時点で観る気を無くしました。そもそもDESTINYがあまりにひどすぎた(クソ劇場版Ζよりは多少マシと言えるレベルの原作レイプぶり)。その続編ということであればもはや恥の上塗り以外の何物でもない。

らんまは2021年4月25日から始まったTVシリーズの再放送が2023年12月31日で終わってしまいましたが、途中で週2本ペースに変わったものの、長かったですね。作画・脚本が悪い回も何度かあったものの、当時のTVアニメの予算を考えると、この長期に渡る放送期間で全体的によく維持されていたと思います。今は1クールだけでも1話で万策尽きるアニメもあるというのに。それより、放送の開始時(1989)と終了時(1992)とで絵柄がだいぶ変わってますね。本放送の当時は特に気にしてなかったけど、今は初期の絵柄のほうが好きです。しかし、これでも原作漫画のエピソードを途中までしか描いていません。乱馬のお母さん・早乙女のどかとのエピソードはOVAで多少補完されていますが、原作漫画には公紋竜やハーブといった強敵もまだ出てきます。ただ、自分はこのオリジナルキャストの面々と当時のセル画の描線や色調が好きなので、たとえ昨今の流行りに便乗して原作通りにリメイクされても絶対に観ることはないですね。若返りのつもりでキャストを一新したとしても、特に八宝斉やコロンばあちゃんを今どきの声優が演じられるとは到底思えません。なお、去年の秋ごろまでは視聴の一言感想を毎週Twitterでつぶやいていましたが、イーロン・マスクの気持ち悪い言動の数々が目に余るようになったのでTwitterも辞めました。天才だかなんだか知らんが、サイコパスに賛同するつもりは毛頭ありません。コイツはフォン・ノイマンと同じ危険な臭いを感じる。

スラムダンクは神奈川の高校バスケ部が舞台なので、tvkのスタッフの中にも推し活をしてくれている古参のファンがいるんでしょうか。スラムダンクのアニメはやっぱり旧キャストのほうが断然いいですね(最近映画化された新しいヤツのほうは1ミリも観てないんですが)。ドラゴンボールの劇場版と同じく、オリジナルエピソードをねじ込むためにパラレルワールドみたいになっているのはご愛嬌。旧アニメのTVシリーズは原作者から評価が低かったそうですが、自分は良い作品だったと思いましたけどね。自分が観たのは小・中学生の頃だったので、作画のことなんて大して気にもしていなかったし、何よりキャストの熱演が素晴らしかった。旧劇場版の作画は今の肥えた目で観ても良好だし、オリジナルキャラクターもよく描けていると思います。そもそもスポーツを題材とした漫画やアニメは特に人体の作画が難しいので、あの東映にしてはよく頑張っていたと思います。赤木剛憲役の梁田清之さんは2022年に亡くなられましたが、惜しい方を亡くしました。獣神ライガーリュウ・ドルク、サムライトルーパーの朱天、ガンダムF91のザビーネ・シャルといった二枚目の敵役でもお馴染みでした。

*1:そもそも田舎にいた頃は、映画はTVで観るか、公民館で上映されるのを観に行くくらいで、劇場なんて年に1回行けるかどうかの贅沢な娯楽でした。ちなみに自分に言わせてもらえば、「のんのんびより」や「だがしかし」の舞台となった地方も鉄道があるだけマシだと思います。本当のド田舎はクルマしか移動手段がありません。そういった限界集落少子高齢化でこの先どんどん消失が加速していくんだと思います。当然土着文化や風習もすべて失なわれます。しかしこの問題から目を逸らして放置する政権をこの30年間ずっと選んできた(あるいはせっかくの選挙権を放棄し続けてきた)のは他ならぬ地方の有権者です。

C++における構造体のコンストラクタはユーザー定義すべきではない(私見)

C++のクラス (class) と構造体 (struct) は、デフォルトのメンバーアクセスレベルが異なるだけなので、機能的な違いはありません*1。構造体でも、メンバー変数だけでなくメンバー関数を定義することができます。当然コンストラクタやデストラクタをユーザー定義することもできます。

コード例:

#include <string>

struct MyStruct {
    std::string name; // デフォルトコンストラクタで空文字列に必ず初期化されるので、コンストラクタのメンバー初期化子リストに記述する必要はない。
    bool isEnabled;
    bool isChecked;
    float positionX;
    float positionY;
    int orderZ;

    // ゼロ相当の値で POD 型メンバー変数を初期化。
    MyStruct()
    : isEnabled()
    , isChecked()
    , positionX()
    , positionY()
    , orderZ()
    {}

// デストラクタをユーザー定義していない場合、コンパイラが自動生成する。
// いずれにせよメンバー変数のデストラクタは自動実行されるため、値型として定義された name メンバーは自動的に破棄される。
};

int main() {
    // 構造体のデフォルトコンストラクタがユーザー定義されている場合、デフォルト初期化したローカル変数の内容は指定した値で初期化される。
    MyStruct s;
}

ただ個人的な意見としては、構造体のほうは基本的に複数のpublicデータをまとめて管理するだけの簡素な使い方(Cの構造体やPascalのレコードとほぼ同様の使い方)をするべきだと思っているので、コンストラクタはユーザー定義しないことのほうが多いです。当然デストラクタもユーザー定義しません。デストラクタをコンパイラに自動定義させる場合、デフォルトで非仮想となり、「基底クラスBaseへのポインタと仮想デストラクタを経由して派生クラスDerivedのオブジェクトをdeleteする」ということ(ポリモーフィックな削除)はできないため、構造体は派生型を定義しない方針で運用します(C++11以降は、Cコードと共有する構造体でなければfinalを付けてしまったほうがよいかも)。C#の構造体は言語仕様レベルで仮想メソッドや派生型を定義できなくなっていますが、C++では運用で実現します。

C# (.NET) では、クラスは参照型、構造体は値型であり、機能および型システム上での明確な違いが存在しますが、使い分けについてのガイドラインもあります。

C++/CLIでは、C#のクラスに相当するref class / ref structと、C#の構造体に相当するvalue class / value structを定義することができます。

Javaには構造体相当機能がないものの、Java 16でレコードクラスが追加されました。ちなみにProject Valhallaでは、値クラスvalue classの導入が検討されているそうです。

Swiftもクラスは参照型、構造体は値型で、明確な違いが存在します。C#の設計が優れていたことが証明されました。

グラフィックスプログラミングでよく利用される、2 - 4次元の座標情報をまとめて管理する固定長のベクトル型(float×2のVector2Fや、double×3のVector3Dなど)は、未来永劫メンバーの数が変わることはないので、こういった変化の小さい汎用的な型に関してはアプリケーションコード側の記述の簡素化のために引数付きコンストラクタや算術演算子オーバーロードを定義することもありますが、将来的な拡張でメンバーが追加されうる構造体に関しては、コンストラクタを書かないほうがむしろ良いのではないかと思います。

もしC++でユーザー定義のコンストラクタやデストラクタが必要になった場合、大抵そのデータ型は構造体ではなくクラスで実現するべき(メンバー変数を隠蔽して、アクセッサーを用意するべき)、ということを示唆しています。特にポインタ型のメンバー変数をpublicにして公開すると、正規の手順を経ることなく外部コードから勝手にでたらめなアドレスを代入したり、ポインタが指している領域を勝手に削除したり、といったことができてしまうため、危険性が増します。

そのコンストラクタ、本当に必要ですか?

C/C++において、組み込みの数値型や任意のポインタ型、あるいはそれらから成る構造体や配列といったPOD型の非静的メンバー変数(インスタンス変数)の内容は、デフォルトでは不定値になります。そのため、クラスの非静的メンバー変数はユーザー定義のコンストラクタにて確実にゼロなどの値で初期化しておくべきです。C++11以降はJavaC#のようにクラス定義ブロック内でメンバー変数に直接初期値を与えることもできるようになったので、こちらを使う手もあります。ただし、コンストラクタのメンバー初期化子リストで指定した値のほうが優先されるというルールがあるため、この機能を利用する場合は混乱を招かないように注意が必要です。

しかし、そもそもゼロ初期化の構文や値初期化の構文を使えば、ユーザー定義のコンストラクタがなくとも、すべてのPOD型メンバーをゼロ相当の値で初期化することができます。これにより、少なくとも不定値による未定義動作だけは防げます。

#include <memory>

struct MyPoint {
    int x;
    int y;
};

int main() {
    // 構造体のデフォルトコンストラクタがユーザー定義されていない場合、デフォルト初期化したローカル変数の内容は不定値になる。
    MyPoint ptBad1;
    // 以下もデフォルト初期化となり、メンバー変数の値は不定値になる。
    std::unique_ptr<MyPoint> ptBad2(new MyPoint);

    // 以下はすべてゼロ初期化される。
    MyPoint ptGood1a = MyPoint();
    MyPoint ptGood1b = {};
    MyPoint ptGood1c{}; // C++11 以降。
    std::unique_ptr<MyPoint> ptGood2a(new MyPoint());
    auto ptGood2b = std::make_unique<MyPoint>();
}

逆に、ゼロ初期化や値初期化の構文を使ったとしても、コンストラクタをユーザー定義しているにもかかわらず、POD型メンバー変数をそのコンストラクタで初期化し忘れていた場合、そのメンバーの値は不定値になってしまいます。詳しくは以下のエントリでも述べていますが、自分が構造体のコンストラクタを積極的に定義したくない理由のひとつがコレです。

sygh.hatenadiary.jp

C/C++は未定義動作や未規定動作の落とし穴があちこちに存在する危険な言語なんですが、その恐ろしさを理解していない不用心なプログラマーが多すぎる。コードレビューしていると、POD型ローカル変数を宣言時に初期化せず不定値のまま使い始めているコードのほか、前述のようにコンストラクタをユーザー定義しているにもかかわらず、POD型メンバー変数をそのコンストラクタで初期化し忘れている(メンバー初期化子リストに記述していないだけでなく、コンストラクタ本体ブロックでも何も代入していない)危険なクラスや構造体もよく見かけます。C++の言語仕様上は合法なのでコンパイル自体は通ってしまうのですが、未定義動作を引き起こす危険なコードです。たとえデフォルト初期化されても不定値にしないようにするフールプルーフ目的でコンストラクタをユーザー定義したつもりが、逆に未定義動作のバグや脆弱性を作り込んでしまうわけです。現在のコンパイラあるいは静的コード分析ツールはこういった危険なコードに対して、ガイドライン警告やlint警告を出してくれるのですが、残念ながら不用心なプログラマーは有用な警告をすべて無視してしまいます。たとえIDE上で分かりやすく強調(ハイライト)表示されていても、彼らは無視してしまいます。なぜさっき書いたコードがハイライト表示されているのか、ということに対して何の疑問も持たないんでしょうね。自分としては、刃物の使い方を知らない状態で不用意に振り回して欲しくないんですよ。要するに、「うかつにコンストラクタを定義するな、もし定義するならば注意を払って実装しろ。新しいメンバーを追加したらコンストラクタも正しくメンテナンスしろ」と言いたい。

ちなみに未初期化のローカル変数や未初期化のメンバー変数を使用すると、MSVCではデフォルトでC4700のコンパイラ警告が出ます。ユーザー定義のコンストラクタで未初期化のメンバー変数が残っている場合、コンパイラ警告は出ませんが、静的コード分析ツールがC26495の警告を出してくれます。GCCでも-Wuninitialized-Wmaybe-uninitializedコンパイラ警告が出ますが、ユーザー定義のコンストラクタで未初期化のメンバー変数が残っている場合、警告が出ません。Clangではいずれも警告が出ませんが、Clang-Tidyのcppcoreguidelines-init-variablescppcoreguidelines-pro-type-member-initがあります。これらの警告が出ているコードは、致命的な問題を引き起こす危険性が高いので、いっそエラーになるように設定してしまったほうがよいと思います。

しかし、変数の宣言時初期化を心がけていれば、フールプルーフのためにコンストラクタをユーザー定義する必要はありません。そもそも、メンバー変数がすべてpublicの構造体を、メンバー変数がすべてprivateのクラスと同じように使えるかのように見せかけること自体が間違いなんです。

構造体のコンストラクタのユースケース1

「ゼロあるいはゼロ相当以外の値をデフォルト時の無効値として使用したい場合」は、構造体のコンストラクタをユーザー定義したくなることがあるかもしれません。たとえばインデックス番号はゼロも有効なので、負数-1を無効値として使いたい場合などが考えられます。
しかし標準C++STLコンテナでは配列インデックスに符号無しのsize_t型を使うので、無効値として負数は使えません。かといって最大値SIZE_MAXを無効値とみなすというのは危険です。もしC++において無効な状態を表現できるインデックスデータの変数を定義したい場合は、C++17のstd::optional<size_t>boost::optional<size_t>を使うべきです。std::optionalのデフォルト値はstd::nulloptboost::optionalのデフォルト値はboost::noneとなるので、これらの型を持つメンバーをコンストラクタで明示的に初期化する必要は特にありません。std::optionalboost::optionalは、有効な値を持たない状態(無効な状態)がありえることを型情報で明確に示しており、また値を持たない状態で値を取得しようとすると、それぞれstd::bad_optional_access例外やboost::bad_optional_access例外をスローしてくれるので安全です。とはいえ、Optional型に有効な値が設定されていても、本当に有効範囲内のインデックスであるかどうか(インデックス値が配列のサイズ以上になっていないか)ということは利用時にチェックするコードを書くべきですが。
一方、C++非標準のAPIや、サードパーティー製のライブラリでは、配列インデックスに符号付きのintint32_t相当を使っているものもあると思います。Optional型よりも軽量でお手軽な数値型を使いたい気持ちは分かりますし、C++以外の他の言語との相互運用*2では単純な数値型のほうが便利ですが、ただの数値型では、無効なインデックス値に何を使うかという外部仕様をいちいちドキュメント化しないといけないし、万が一負数を使って配列にインデックスアクセスしてしまった場合、C/C++では未定義動作を引き起こします。

構造体のコンストラクタのユースケース2

「ユーザー定義されているものの、メンバーを適切に初期化しておらず、不定値になってしまう怠惰なデフォルトコンストラクタを持つ型」をメンバー変数に持つ構造体の場合はどうでしょうか。このようなメンバー変数は、前述のようにゼロ初期化や値初期化の構文を使っても不定値になってしまいます。そのため、構造体にコンストラクタを定義し、メンバー初期化子リストで引数付きコンストラクタを使用して初期化するなどの対処をする必要があります。とはいえ、そもそもこのような怠惰なコンストラクタをユーザー定義するのは邪悪な設計の典型であり、ましてや怠惰なコンストラクタを持つ邪悪な型を構造体に含めるべきではありません。どうしてもそのような邪悪な型の変数をメンバーに含めなければならない場合、構造体ではなくクラスとして定義し、そのメンバー変数は外部から直接アクセスできないように隠蔽すべきです。

ちなみにC#の構造体はバージョン9まで、引数のないコンストラクタ(デフォルトコンストラクタ)はユーザー定義できず、デフォルトコンストラクタでは構造体のフィールドは必ずその型の既定値で初期化される仕様になっていました(C#では、構造体でもクラスでも、特に指定がないかぎり、数値型や列挙型のフィールドはゼロ相当の値で初期化され、参照型のフィールドはnullで初期化されます)。デフォルトコンストラクタがユーザー定義できるようになったのはC# 10からです。

構造体のコンストラクタのユースケース3

「構造体に引数付きコンストラクタを定義しておくと、メンバーを追加したときに引数を増やすので、既存コードのコンストラクタ呼び出し箇所がコンパイルエラーとなり、修正すべき箇所が分かりやすくなる」という意見もあるかもしれません。しかし、もしメンバーをN個増やしたと同時にN個減らした場合、コンストラクタの引数の数は変わらないので、場合によっては既存コードのコンストラクタ呼び出し箇所を修正しなくてもコンパイルが(意図せず)通ってしまう可能性もあります。

struct MyPie {
    double centerX;
    double centerY;
    double radiusX;
    double radiusY;
    //double sweepAngleDegrees; // 旧メンバー。
    double sweepAngleRadians; // 新メンバー。

#if 0
    // 旧コンストラクタ。
    explicit MyPie(double centerX, double centerY, double radiusX, double radiusY, double sweepAngleDegrees)
    : centerX(centerX)
    , centerY(centerY)
    , radiusX(radiusX)
    , radiusY(radiusY)
    , sweepAngleDegrees(sweepAngleDegrees)
    {}
#else
    // 新コンストラクタ。
    explicit MyPie(double centerX, double centerY, double radiusX, double radiusY, double sweepAngleRadians)
    : centerX(centerX)
    , centerY(centerY)
    , radiusX(radiusX)
    , radiusY(radiusY)
    , sweepAngleRadians(sweepAngleRadians)
    {}
#endf
};

int main() {
    // degrees で指定する旧コード。そのままでもコンパイル自体は通ってしまい、仕様変更に気づかない。
    MyPie pie1(100, 200, 40, 30, 180.0);
    // コンストラクタがユーザー定義されているかどうかにかかわらず、C++11 以降は以下のように書くこともできる。
    MyPie pie2{100, 200, 40, 30, 180.0};
    // コンストラクタがユーザー定義されていない場合、あるいは C++11 以降で explicit 指定されていない場合、以下のように書くことができる。
    //MyPie pie3 = {100, 200, 40, 30, 180.0};
}

Objective-CやSwiftのメソッドは、引数にラベルを付けることができ、オーバーロードは引数の型ではなくラベルによって実現しますが、メソッドのラベル(セマンティクス)を変更するとメソッドインターフェイスの互換性が破壊され、既存のコードがコンパイルエラーになる(破壊的仕様変更になる)ので、修正すべき個所がすぐに分かるようになっています。一方、JavaC#など、C++系統の言語におけるメソッドやコンストラクタは、引数の型が異なる場合にオーバーロードを定義でき、引数の名前はシグネチャに影響しないので、もし引数におかしな名前やスペルミスを含む名前を付けてしまったときでも、メソッドインターフェイスの形式的な(型システム上の)互換性を維持したままこっそりリネームするといったことができますが、当該引数の意味や使い方(仕様)を変えても型が同じ(または暗黙変換可能な型同士)だった場合は既存コードのコンパイルが通ってしまい、修正すべき個所がすぐには分かりません。どちらの言語系統の仕様も、メリットとデメリットがありますが、互換性が破壊されたはずなのに既存コードのコンパイルが通ってしまうのは明らかに問題があります。
C++/Java/C#においてうかつにメソッドやコンストラクタのオーバーロードを定義するのは危険です。公開APIのようにファーストパーティ以外にも広く使われている場合、互換性のない機能追加や仕様変更を実施したときはコンパイルし直してもらう必要がありますが、既存のオーバーロードのせいで身動きがとれなくなってしまう可能性があります。こういうときは引数付きコンストラクタの代わりに異なる名前を持つファクトリ関数を別々に用意したほうがよく、上記のコード例では初期化したMyPie構造体のインスタンスを戻り値で返すMakeMyPieWithSweepAngleDegrees()MakeMyPieWithSweepAngleRadians()を定義するべきです。オーバーロードとは異なり、型システム上で同じシグネチャを持つ新ファクトリ関数を定義しても、旧ファクトリ関数を維持したままにすることができます。

そもそもdegreeをあとからradianに変えるような互換性破壊をするなよ、というのはもっともですが、特に非公開コードにおいて、機能追加や仕様変更によって既存コードの互換性を破壊せざるを得ないことはどうしてもあります。そういった互換性破壊をしたとき、容易に気づけるようなコードになっているかどうか、という点で、クラスや構造体の引数付きコンストラクタのオーバーロードには潜在的な拡張性欠損の問題がある、ということを述べているわけです。

余談ですが、個人的に引数付きのコンストラクタは、コピーコンストラクタとムーブコンストラクタを除いて、一律explicitを付けるようにしています。explicitがない場合、引数の型のオブジェクトから暗黙変換することができる「変換コンストラクタ」(converting constructor) となりますが、特に引数が1つだけの場合、意図しない変換による問題を引き起こすことが多いです。なお、C++11以降は、複数の引数を持つコンストラクタにexplicitがない場合、初期化子リスト{...}から暗黙変換することもできるようになっています。とはいえ、コンストラクタを呼び出しているのかどうかが判然としないので、個人的には好みではありません。ほとんどの場合、暗黙変換はメリットよりもデメリットのほうが大きすぎて、採用する積極的な理由がありません。

ちなみにC/C++の構造体の初期化には、初期化子リストを使うこともできますが、構造体のメンバーの並びを変えたときなどに既存コードの互換性が崩れるので、いったんゼロ初期化してから各メンバー変数に明示的に代入するコードのほうが安全です。コンパイラ最適化が効けば二重初期化のコストはかかりません。C++11以降の一様初期化 (uniform initialization) も同様です。Windows APIのように、バイナリレベルの後方互換性を維持する必要のあるAPIでは、構造体の既存メンバーの並びを変えたり削除したりするようなことはせず、新しいメンバーを追加する場合であっても必ず末尾に追加するようになっていますが、バイナリレベルの後方互換性を維持する必要のないユーザー定義のコードでは、リファクタリングの際に既存の構造体のメンバーの並びを変えたり削除したりすることもあります。構造体に新しくメンバーを追加する場合、不要なパディングが入らないよう、領域節約のためにメンバーの並びを調整したほうがよいこともあります。前述の例のようにメンバー変数を増減させたときも、メンバー変数の名前をコード上で明示しておけば、仕様が変わったときにコンパイルエラーになるため、修正すべき個所がすぐに分かります。メンバーアクセスの記述量は増えますが、リテラルをだらだら並べるよりも遥かに可読性が高いと感じます。

struct MyPie {
    double centerX;
    double centerY;
    double radiusX;
    double radiusY;
    //double sweepAngleDegrees; // 旧メンバー。
    double sweepAngleRadians; // 新メンバー。
};

int main() {
    MyPie pie1 = {};
    pie1.centerX = 100;
    pie1.centerY = 200;
    pie1.radiusX = 40;
    pie1.radiusY = 30;
    // degrees で指定する旧コード。コンパイルエラーになるので仕様変更に気づきやすい。
    pie1.sweepAngleDegrees = 180.0;
}

C++20では、C99やC# 3.0以降/VB 9.0 (2008) 以降のように、初期化子リスト内でメンバー名を指定できるようになりました(指示付き初期化: designated initialization)。C99の指示付き初期化と比べると融通が利かず、若干機能面で劣りますが、これはもうC++11でさっさと導入しておけばよかったのではないかと思います。

従来の初期化子リストやコンストラクタ呼び出しと違い、各メンバーに明示的代入する方式では、構造体変数をconst修飾やconstexpr修飾することができないという欠点がありますが、指示付き初期化は両者の良いところ取りができる優れた方法です。

さらにC++20では、コンストラクタがユーザー定義されていなくても、ブレース{...}ではなくパーレン(...)を使って集成体の初期化ができるようになりましたが、前述の理由から指示付き初期化のほうがお勧めです。

ちなみにVBDelphiには省略記法としてのWith文がありますが、スコープが分かりづらいなどの問題点もあります。

再掲:そのコンストラクタ、本当に必要ですか?

上記のように、構造体のコンストラクタをユーザー定義するユースケースはいくつか考えられるものの、逆にメンテナンス性が低下するデメリットのほうが大きいため、やはりユーザー定義すべきではないと考えています。

もうひとつ、構造体のコンストラクタを定義したくない理由のひとつとして、constexprでないコンストラクタをユーザー定義すると、その型のインスタンスはconstexprにできない(コンパイル時定数にできない)ということが挙げられます。constexprは暗黙的にインラインでなければならないので、もし構造体をヘッダーファイルに定義している場合は、そのconstexprコンストラクタの定義をヘッダーに書く必要があります。コーディング規約で関数の実装をヘッダー側ではなくソースファイル側に書くよう定めている場合もあると思いますが、そのようなルールはconstexprコンストラクタと相性が悪いです。constexprコンストラクタを定義するか、あるいはコンストラクタを一切ユーザー定義していなければ、初期化子リストもしくはconstexprファクトリ関数を使ってconstexprインスタンスを生成することができます。

結論:C++を捨てましょう

C++は本当にめんどくさいですよね。でも大丈夫です。世の中には、もっと安全で効率が良く、コンパイルも高速で実行速度も十分な後発プログラミング言語がたくさんあります。ほとんどの場合、あなたの書いているコードはC++である必要はありません。単に古いコード資産を捨てるのが億劫なだけでしょ。必要最低限のものだけ残して、あとは全部捨ててください。C++はバックエンドのためだけにある言語です。フロントエンドをC++で書くなど狂気の沙汰です。

*1:もともとCに毛の生えた「C with Classes」から発展・分化したのがC++なので、いびつで非合理的・不条理とも言える設計が言語仕様のあちこちに散見されます。

*2:JavaC#と相互運用する場合は、コレクションのインデックスには32bit符号付き整数型を使うことが多いと思います。.NET documentation C# Coding Conventions - C# | Microsoft Learn

マイクロソフトの機械翻訳は害悪でしかない最低最悪の品質です

DeepLは比較的精度の高い機械翻訳で、pixivでもときどきお世話になっています。Google翻訳はそれより一段劣りますが、長文の意味をザックリ理解したいときに使うことがあります。
一方、マイクロソフト機械翻訳は比較にならないくらいお粗末で、最低最悪レベルです。エンドユーザー向けのドキュメントの翻訳もクソなんですが、開発者向けのドキュメントの翻訳はそれ以上にクソofクソです。不正確で役に立たないどころか、逆に害悪すらもたらすレベルで、むしろ英語原文を直接読んだほうが手っ取り早い。コイツが垂れ流し続ける邪悪な機械翻訳による検索汚染にはそれこそ毎日のように悩まされ続けています。無能とかいうレベルを遥かに超越している。仕事の邪魔でしかない。激しい頭痛と吐き気がして、PCをズタズタに破壊してレンジでチンしたくなる衝動に駆られます。

現行の英語⇔日本語の機械翻訳における致命的な欠点は、肯定と否定をまるで反対に訳すことがあるという、中学生どころか小学生ですら明らかに間違っていることが分かるようなポカミスを堂々とやらかすことです。マイクロソフトの場合はそういうところが他の機械翻訳サービスと比べて顕著で、極端に低品質です。こんなクソ翻訳をありがたがって使う日本人は情弱以外の何者でもない。

Windowsを休止状態から復帰させると、タスクトレイの通知領域に赤いエクスクラメーションマーク(!)のアイコンが表示されていることがあります。このアイコンにマウスオーバーすると、以下のようなツールヒントメッセージが表示されます。

お使いのデバイスには、重要な更新プログラムがありません

はあ。そうですか。じゃあWindows Updateの必要はないんですね。

「重要な更新プログラムがありません」といったな。あれは嘘だ。

実はこの通知が来たとき、重要な更新プログラムの配信がWindows Updateのバックグラウンドサービスによって検出されています。
なので、本来の意味は「重要な更新プログラムがあるので、早めにWindows Updateを実行してください」ということなんです。

このメッセージ、英語版の原文ではどうなっているかというと、

Your device is missing important updates.

だそうです。
この「miss」という動詞は、「見落とす」「見逃す」「恋しく思う」といった意味を持ちます。要するに、上記メッセージは「見逃している重要な更新がありますよ」ということを意味しています。

しかしながら、あろうことかマイクロソフトは日本語版において「ありません」というヘタクソな表現に翻訳してしまったようです。
日本語を母語とする普通の日本人であれば、「更新プログラムがありません」と言われれば、「更新プログラムがインストールされていません」という意味にはとらえず、「更新プログラムが配信されていません」という意味にとらえるのではないかと思います。
バカな人間が深く考えずに翻訳したのか、それともバカな機械翻訳にかけたのかは知りませんが、こういうアホな翻訳・直訳は昔から日本語版Windowsのあちこちにあります。Microsoft 365(旧Office Online)もグダグダです。特にOneDriveの翻訳がお粗末すぎる。機械翻訳が丸わかり。

情報技術の世界では、日本語は情弱が使うマイナー言語のひとつに過ぎないので、OSのUI言語はいっそ日本語ではなく英語にしたほうがよいかもしれないですね。英語の勉強にもなります。自分は普段使っているモバイルOSの言語を英語にしています。

ちなみにWindowsでは、UI言語とは別にシステムロケールを設定できるようになっており、マルチバイト文字のデフォルトエンコーディングはこのシステムロケールに従います。
Unix系OSでは現在UTF-8が一般的ですが、Windowsではいまだに言語固有のANSIコードページがデフォルトとなっています。Windows 10(バージョン1803以降)ではシステムロケールUTF-8に設定することもできるようになっていますが、いまだベータ版です。
古いWindowsアプリやゲームの中には、システムロケールが日本語(CP932)であることが前提になっているものもあるので、システムロケールは変えないほうがよいと思います。

中国語の漢字と日本語の漢字

ネットが使える人口の比率は不明ですが、日本の総人口は1億ちょい、中国の総人口は14億です。両国とも現在人口が減少しつつあるものの、言語の絶対的なユーザー数は日本語よりも中国語(簡体字)のほうが圧倒的に多いです。しかも日常的に日本語を使っているのはほぼ日本人だけです。Web上における日本語の存在感や重要度は極めて低い。そのためか、Web上のサービスは、記事のタイトル文字列をもとにASCII英数字のURLを生成する際、日本語の記事内容であるにもかかわらず、日本語の読み(kanji)ではなく中国語の漢字(hànzì)すなわちピンイン読みとして機械的に処理してしまう間抜けな実装になっているものがあります。

だからー、URLをローカライズすんなよな。バカなんですか? 死ぬんですか?
Unicodeが普及してからは、URLにマルチバイト文字を含める邪悪なWebサイトが蔓延するようになりましたが、それと大差ないです。URL自体は英語オンリーまたはUUIDにして、en-usやja-jpのような言語IDをURLの一部やパラメータに指定することで変えられるようにすればいいだけなのに。

ちなみにMicrosoftサポートの言語別ページのURLには、タイトルに相当するマルチバイト文字が含まれますが、タイトル部分を引っこ抜いてページUUIDだけにしたURLを使えばリダイレクトされるようになっています。ただしこのサポートページは機械翻訳だらけで、タイトル文字列も誤訳のオンパレードです。英語版は役に立ちますが日本語版は嘘八百だらけでほとんど役に立ちません。
support.microsoft.com

マウスのプロパティに直接アクセスする

WACOMタブレットドライバーはときどきおかしな状態に陥り、タブレット操作がまともにできなくなることがあります。このとき、なぜかマウスの「ポインターの精度を高める」というオプションのチェックも勝手に外れる現象が発生します。スリープ(休止状態)から復帰したときや、OSの再起動後などに発生することがあります。自分はマウスでも細かい操作をすることが多いので、このオプションはONにしています。

タブレットドライバーの問題はタスクマネージャーから「WTabletServicePro」のサービスを再起動すれば大抵解消するのですが、「ポインターの精度を高める」は外れたままです。
この場合、Windows 10では「設定」→「デバイス」→「マウス」→「その他のマウス オプション」とたどっていくことでようやく設定ダイアログが表示されるのですが、操作がダルすぎる。
従来の「コントロール パネル」→「ハードウェアとサウンド」にて、「デバイスとプリンター」の直下にある「マウス」をクリックすることでもダイアログが表示されます。こちらのほうが手っ取り早い。

Windows 10の「設定」は、いまだに従来のコントロールパネルとまともに統合・整理されておらず、詳細な設定は結局コントロールパネルが必要となるので、自分はスタート画面(スタートメニュー)にピン留めしています。
「設定」→「個人用設定」→「テーマ」の「デスクトップ アイコンの設定」にて、デスクトップにコントロールパネルのショートカットを表示させることもできます。

実は、コントロールパネルを使って「マウス」へのショートカットをデスクトップに作成することができます。
miyabiymo.com

なお、コントロールパネルをスタート画面にピン留めしておくと、右クリックしたときに最近使った項目が表示され、項目にマウスオーバーしたときに表示される右端の押しピンアイコンをクリックすることでピン留めすることができます。ここに「マウス」をピン留めしておくという手もあります。

C/C++の汎整数拡張(昇格)と条件(三項)演算子

現在主流のプロセッサは、異なる型の数値同士を直接演算することはできません。必ず型を揃える必要があります。
C/C++は暗黙の型変換を許すパターンが多数ある危険な言語なんですが、中でも異なる整数型、特に符号付きと符号無しの整数型を混ぜると、暗黙的な型変換により意図しない結果をもたらすことが多々あります。

最も有名なパターンが以下です。

#include <stdio.h>

int main(void) {
    signed int x = -1;
    unsigned int y = 1;
    printf("%d\n", x < y);
    return 0;
}

一見すると、正数を保持しているyよりも負数を保持しているxのほうが小さいので、比較結果は真(1)となるように思えます。
しかし、C/C++では汎整数拡張(汎整数昇格)のルールに基づき、実際には暗黙的にunsigned intに揃えられてから比較されるため、-1UINT_MAX(規格上は少なくとも65,535以上、現在主流の32bit/64bit向けの処理系では4,294,967,295)に暗黙変換され、比較結果は偽(0)となります。

C/C++ではこのような直感に反する動作がしょっちゅう発生するので、注意深く扱わなければならない危険な言語なんですが、大抵のプログラマーはこんな細かい仕様にまで気を配らないので、いとも簡単に潜在バグを埋め込んでしまいます。上記の例では、xyの値がともにゼロ以上の場合は不具合が発覚せず、負数を含むテストを実施しないとバグが顕在化しないところが厄介です。
代表的なコンパイラはこういった危険なコードに対して警告を出してくれますが、警告ではなくエラー扱いにしておくのが安全です。

条件演算子三項演算子

条件演算子三項演算子cond ? x : yにも同様の罠が潜んでいます。

#include <stdio.h>

int main(void) {
    signed int x = -1;
    unsigned int y = 1;
    long long z = ((long long)x < (long long)y) ? x : y;
    printf("%lld\n", z);
    return 0;
}

前回の罠を踏まえ、今回はちゃんと比較前にキャストして型を揃えています。また、結果を格納する変数zの型を、signed intおよびunsigned intの両方を格納するのに十分な長さを持つlong long型にしました。なので、結果は-1になるような気がしますよね?
残念ながら結果はUINT_MAXとなります。上記の例では、条件演算子の結果は暗黙的にunsigned intに揃えられるからです。
もし上記の例でzの型指定にC++11以降のauto型推論を使った場合、unsigned int型になります。

なお、C++で単に小さいほうの値や大きいほうの値を選択したい場合、このようなつまらない凡ミスを防ぐためにも、わざわざ自前で式を書くようなことはせず、std::min()std::max()を使うべきです。

C#

ちなみに、C#は上記のような直感に反する危険な動作をしません。32bit符号付きのintと32bit符号無しのuintを比較する場合、いったん64bit符号付きのlongに揃えられてから比較されるので、直感に則した正しい結果が得られます。さらに64bit符号付きのlongと64bit符号無しのulongを直接比較することはできません。明示的な型変換が必要となります。
また、intの式とuintの式を条件演算子の第2項と第3項に混在させることはできず、コンパイルエラーになります(C#では情報が失われる暗黙変換は許可されないため)。

int x = -1;
uint y = 1;
System.Console.WriteLine(x < y); // True
var z = (x < y) ? x : y; // コンパイルエラー。
System.Console.WriteLine(z);
System.Console.WriteLine(z.GetType());

C#でも、単に小さいほうの値や大きいほうの値を選択したい場合、Math.Min()Math.Max()を使うべきです。

安全なコードを書く自信がない場合、過去のしがらみにとらわれた時代遅れのC/C++はさっさと捨てて、他のモダンな言語に移行しましょう。

男性向け下着のバリエーションの少なさは異常

男に生まれて損だなと思う理由のひとつは、女性と比べて衣服の選択肢が圧倒的に少ないことです。女性はスカートでもパンツルックでも好きなほうを選べますし、なんだったらメンズ服を着ることさえもできますが、現代では男がスカートをはいていたりフリルやリボンのついたブラウスを着ていたりしたら変人扱いされるだけです。自分は性同一性障害ではないのですが、次に人間に生まれ変われるならば女のほうがいいなと思っています。もちろん女性は女性で、生理など大変な苦労をされている点もあることは一応理解しているつもりなのですが。
まあそれは置いておくとして、一番許せないのは男性向け下着のバリエーションが少ないこと。毎日身に付けて、大切な股間を守る基本的なアイテムのはずなのに、デザインは大別してボクサーパンツかトランクスぐらいしかなく、しかも色はせいぜい黒/グレー/ネイビーぐらい。くすんだ暗い色しかありません。せめてターコイズブルーくらいはラインナップに入れてくれ。自分は締め付け感が嫌いなのでトランクス派なのですが、ボーダーやチェックなどの柄物は嫌いで、無地のほうが好みです。さらに素材もポリエステルのツルツル感が嫌いなので、柔らかくて伸縮性に富み、汗をしっかり吸う綿100%ニットしか受け付けません。腰のゴム部分もニットでないと許せない。ですが、そういった諸条件を考慮してダメなものを除外していくと、最後に残る選択肢はほんのわずかです。

衣料品メーカーは明らかに男性向け下着に対して手を抜いています。それもこれも、下着に無頓着な男性が多いことが原因なのではないかと思っています。この状況を打破するには声を上げていくしかない。

あと傘も男性向けはカラーバリエーションが乏しいんですよね。昔はユニクロもカラフルな傘を取り扱っていて、明るい黄緑が気に入ったので使っていましたが、今は定番のくすんだ色しかありません。調和という名の同調圧力を重んじる閉鎖的な日本社会において、男女兼用としては無難なのかもしれませんが、こんな色の傘を差していたら気分が駄々下がりです。今はスーパーで買ったライラック(薄紫)の折り畳み傘をかばんに忍ばせたりしています。日傘も外側が明るめのブルーグレーを使っています(コンビニで買ったにしては頑丈で長持ち)。男だってカラフルで可愛いアイテムを使いたいんです。「男だったら黙って黒やネイビーにしろ」っていう価値観の押し付けはやめて欲しい。

アベの国葬? 冗談じゃあない

ヤツに関わりのある悪質なカルト宗教団体「統一教会」とやらは魑魅魍魎の温床なんでしょ? しかしコイツ、日本会議と同じく、やたら気持ち悪い団体に関わりがありますね。叩けばどんどんホコリが出てくるだろう。そもそも現与党の公明党も支持母体が創価学会*1ですからね。宗教団体は儲かるし、何も考えていないバカな信者どもが家族ぐるみで票田になってくれるからチョロいんでしょうね。とにかく日本は政治について真面目に考えている人が少数派、ということが最も致命的な問題なのだと思います。もはや民主主義の体をなしていない。多数決の悪い側面だけが反映されてしまっています。

diamond.jp

そもそもアベは日本を破壊した元凶と言える政治家の一人です。いわば戦犯でしょ。公文書の偽造すらも平気でやる犯罪者です。そんな国賊が一体どうして国葬なのか。国民の目を自公政権の闇という邪悪な本質から逸らそうと考えているようにしか思えない。岸田も所詮は自民だからな。だいたい、安倍氏は総理大臣の任期途中、しかも肝心な場面で二度も逃げ出して職を放棄しています。もともと身体が弱いことが分かっているのであれば、最初から総理大臣などになるべきではないでしょう。少なくとも内閣総理大臣っていうのは日本で一番プレッシャーのかかるポジションです。そんなことは小学生でも理解できる話です。

sygh.hatenadiary.jp

以前「呪われて地獄に落ちろ」と書きましたが、本当に地獄に落ちてくれました。まさに自業自得だったのだと思います。殺されて当然、とまでは言いませんが、任期は長かったものの大したことはやっていないどころか、日本の社会制度とインフラを破壊し続け、経団連とともに甘い汁を吸い尽くし、社会的に見捨てられた弱者たちから目を逸らしてきた結果が、今回の人誅という形で現れただけではないでしょうか。ただ、「人を呪わば穴二つ」とも言います。ヤツの罪をあばくことは必要ですが、感情的な死体蹴りにならないよう、冷静に分析することが最も重要だと思います。

まずは今回の銃撃が、公権力の締め付け強化につながらないように監視していく必要があるのですが、日本国民は同調圧力が大好きなうえにマゾが多いですからね。やっぱり戦闘機に竹槍で立ち向かおうとするくらいですから。どうも自由よりも束縛を選ぶ連中が多すぎるようです。原発事故で自分たちの住む土地がダメになっても、相変わらず原発推進派の自民党に投票するほどのマゾっぷり。これではお先真っ暗ですね。日本人は本当にバカなマゾが増えました。何も考えていないマゾ人間なので「政治家を銃殺するなんて、民主主義への挑戦だ! 改憲して国民の権利を制限・剥奪しなければならない!」とかいうアホな論理にも簡単に騙されそうです。今回、自民党が邪悪なカルト宗教を利用して組織票集めしていることが明るみに出たんですが、それでもまだ自公に投票する寝ぼけた連中がいることのほうにこそ絶望感を覚えます。

*1:大学生のとき、クラスメートに創価学会の信者がいましたが、見事に洗脳されていました。やっぱり宗教にハマる人間はチョロいんです。