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

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

東池袋自動車暴走死傷事故 厳罰化要望に署名しました

ameblo.jp

自分も微力ながら郵送にて署名に協力させていただきました。

署名用紙 (PDF) は以下からダウンロードできます。署名活動は8月末までを予定されているそうです。
このブログをご覧の方々、よろしければぜひ。

加害者の飯塚幸三は反省も謝罪もせず、それどころか権力で殺人を揉み消そうとしています。
そのような蛮行、決して許されるべきものではありません。

enumの読み方・発音はイニューム

ほとんどのプログラミング言語には列挙型 (enumeration type) あるいは列挙体 (enumeration) というデータ型があり、有限集合を順序付けて定義するのに使用します。C系の言語ではキーワードenumを使用します。

/* 軍艦の種類を表す列挙型 */
typedef enum MyWarshipType {
    MyWarshipType_BattleShip, /* 戦艦 */
    MyWarshipType_AircraftCarrier, /* 空母 */
    MyWarshipType_LightAircraftCarrier, /* 軽空母 */
    MyWarshipType_HeavyCruiser, /* 重巡洋艦 */
    MyWarshipType_LightCruiser, /* 軽巡洋艦 */
    MyWarshipType_Destroyer, /* 駆逐艦 */
    MyWarshipType_Submarine, /* 潜水艦 */
    /* C99 や C++11 では末尾要素の後のコンマが許可される */
} MyWarshipType;
/* 列挙型変数の例 */
MyWarshipType type = MyWarshipType_HeavyCruiser;

列挙型の各メンバーは列挙子 (enumerator) と呼ばれます。C/C++では各メンバーに整数値が割り当てられ、ユーザーコードで明示的に指定しなければ最初のメンバーの内部数値はゼロとなり、以降のメンバーは 1, 2, 3, ... のように自動的にインクリメントされた連番が割り振られます。

Javaには当初列挙型がありませんでしたが、有用性を認めたのか、Java 1.5で結局導入されました。ただしJavaの列挙型は参照型つまりclassの一種であり、そのままでは整数型と相互に変換することはできません。

C#には当然最初から列挙型があります。C#の列挙型はC/C++と相互運用しやすい値型となっていて、キャストだけで整数型と相互に変換することもできます。

C++ではC言語互換のenumをサポートするほか、C++11ではスコープを持つ列挙型 (scoped enum) が導入され、JavaC#に近い記述ができるようになりました*1

enumの発音

ところで、このenumをどう発音しますか?

もともとenumは、前述のように英語の enumeration から来ています。米国式発音では「inùːməréiʃən」、英国式発音では「injùːməréiʃən」です。もしカナで無理やり表記するとすれば、それぞれ「イヌーメレイション」「イニューメレイション」といったところでしょうか。アクセント(第1強勢)の位置は-tionの前、第4音節にあります。
また、enumerator の発音は「inúːmərèitər」もしくは「injúːmərèitər」であり、カナ表記するならば「イヌーメレイター」「イニューメレイター」といったところです。アクセントの位置は第2音節にあります。
動詞形 enumerate の発音は「inúːmərèit」もしくは「injúːmərèit」です。カナ表記するならば「イヌーメレイト」「イニューメレイト」といったところです。

したがって、enumは「inúːm」もしくは「injúːm」と発音するのが正しいと思います。カナ表記するならば「イヌーム」もしくは「イニューム」です。個人的には「イニューム」推し*2ですが、米語圏エンジニア相手の会話であれば「イヌーム」と発音するほうが伝わりやすいかもしれません。
ちなみに.NETにはIEnumeratorインターフェイスおよびIEnumerableインターフェイスが存在しますが、自分はそれぞれ「アイ・イニューメレイター」「アイ・イニューメラブル」と発音しています。
また、Windows APIにはトップレベルウィンドウを列挙する関数EnumWindows()が存在しますが、自分は「イニューム・ウィンドウズ」と発音しています。

たまに見聞きするのが、「イナム」とか「イーナム」とかいう発音ですが、これは正直ダサいと思います。略す前の原語を知らずに無理やり字面だけを読んでいるようで、個人的に言わせてもらうと教養を感じさせない、悪く言うと幼稚な印象さえ受けます。
ネイティブでもenumの発音に関しては諸説あり、「イナム」はともかく「イーナム」のほうはネイティブも使っている人がいるようで、誤りとまでは言えないようですが、個人的には嫌いな発音です。

というわけでenumイニューム。異論は認め……ない。どうしても「イーナム」と発音したい人は、せめてenumの語源と、enumeration, enumerator, enumerateの正しい発音くらいは把握したうえでどうぞお好きなように。

余談:charの発音

文字型charの発音に関しても意見が分かれるかもしれません。自分は語源の character そのまま、すなわち「kǽrəktər」=「キャラクター」と発音しています。Bjarne Stroustrupを始め、ネイティブでも字面だけを読んで「tʃɑ́ːr」=「チャー」とかいう人もいますが、これもまたダサいです。日本では「キャラ」と略す人もいるようですが、これは英語圏・米語圏では通用しないと思います。
ちなみにwchar_tは「ダブリューキャラクター・アンダーティー」もしくは「ワイドキャラクター・アンダーティー」と発音しています。

*1:このscoped enumは、もともとマイクロソフトが開発した.NET用マネージ言語C++/CLIにおいて、マネージ列挙型のために拡張された構文がベースになっています。

*2:自分の学部生の頃の恩師である助教授(当時)が「イニューム」と発音されていたので、その影響も少なからずあります。

ログ用に時刻を取得して文字列化する (C#, Java, POSIX/Win32)

アプリケーションのデバッグや、デプロイ後の問題解析に最も重要な役割を果たすのはログです。ログの機能や精度・粒度によって、問題解決のしやすさがほぼすべて決まります。また、ログは単なるトラブルシューティングだけでなく、場合によってはユーザー操作記録などの簡易的なエビデンス(証拠)としても使えます*1
通例、動作ログにタイミング情報を記録する際、レコード行に時刻情報を含めることで、状態遷移の正しさの確認や速度性能解析に役立てることができます。いわゆるprintfデバッグです。レコードの時刻情報はログをファイルに記録するときだけでなく、デバッグ出力するときにも役立ちます。

また、秒単位ではなくミリ秒単位の時刻情報が記録できると性能解析に便利です*2。なお、時刻は協定世界時 (UTC) で記録しておくと逆シリアライズが簡単になるというメリットがあるのですが、現地時刻(ローカル時刻)とタイムゾーン情報(GMTからのオフセット)を記録する方式のほうが、人間がログレコードを見て判断しやすくなるというメリットがあります。

ログファイルのフォーマットはパフォーマンスの観点から、ファイルの全書き換えではなくプレーンテキストの末尾追加で済むCSVフォーマットやTSVフォーマットなどが好ましいです。また、後で回収できるように、日付ごとにファイルを分けて作成したり、直近数か月分を保持して古いものは自動削除する循環ログにしたり、といった機能を実装します。自分はロギング機能を自前で実装したことが何度かありますが、通例 log4jlog4net を使っているプロジェクトも多いのではないかと思います。

今回はロギングの実装までは踏み込まず、C#, Java, C/C++におけるそれぞれの時刻情報取得と文字列化(フォーマット)についてのみ説明します。
ちなみにISO 8601は日付と時刻の間にTを入れるルールになっているのですが、今回は見やすさ優先のためASCII空白0x20にしました。

C#

C# (.NET) は後発なだけあって一番簡単です。洗練されていて美しいですね。余計な解説をするようなことはほとんどありません。

using System;

public class DateTimeFormatTest {
    public static void Main() {
        const string format = "yyyy-MM-dd HH:mm:ss.fffzzz";
        // GMT からのオフセット値が 0 の場合、タイムゾーンは "+00:00" になる。 
        Console.WriteLine("Local = " + DateTime.Now.ToString(format));
        Console.WriteLine("UTC   = " + DateTime.UtcNow.ToString(format));
    }
}

もしローカル時刻とUTC時刻との間の変換が必要な場合、TimeZoneInfo.ConvertTimeFromUtc()TimeZoneInfo.ConvertTimeToUtc()を使います。

なお、ラウンドトリップ書式指定子 "O", "o" を用いることで、簡単にISO 8601形式の書式化(シリアライズ)と解析(逆シリアライズ)ができるようです。

Java

Javaも時刻関連ライブラリは最初期から実装・標準化されています。しかしタイムゾーンの扱いが若干厄介な印象を受けます。ログのパーサーを書くときに考慮する必要があります。
注意点として、SimpleDateFormatはスレッドセーフではないので、スレッド間でインスタンスを使いまわすのは厳禁です。スレッドごとにインスタンスを作成する必要があります。

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

public class Main {
    private static final String FORMAT = "yyyy-MM-dd HH:mm:ss.SSSXXX";
    public static void main(String[] args) {
        // GMT からのオフセット値が 0 の場合、タイムゾーンは "Z" になる。 
        System.out.println("Local = " + (new SimpleDateFormat(FORMAT, Locale.ROOT).format(new Date())));
        final SimpleDateFormat sdf = new SimpleDateFormat(FORMAT, Locale.ROOT);
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        System.out.println("UTC   = " + sdf.format(new Date()));
    }
}

C/C++

.NETもJavaもプラットフォーム非依存の高水準環境であり、標準ライブラリも充実しているので特に困るようなことはありませんが、問題はC/C++です。こいつらは標準化がとにかく遅い言語で、時刻関連の標準ライブラリは貧弱の一言です。まともな情報を取得するには、プラットフォームごとのライブラリやAPIを使わざるを得ません。

POSIX

UNIX時刻を表すtime_t型からtm構造体への変換がミソです。ちなみに逆変換をする関数mktime(), timegm()も存在します。

なお、標準Cライブラリのlocaltime(), gmtime()はスレッドセーフでないので使わないようにします。代わりにリエントラントなlocaltime_r(), gmtime_r()を使います。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//#include <sys/time.h>

int main() {
    printf("sizeof(time_t) = %d\n", static_cast<int>(sizeof(time_t)));
    // 秒単位まででよければ、time() 関数を使う。
    //const time_t now = time(nullptr);
#if 0
    // C 標準では time_t の実際の型が整数であるかどうかは規定されていない。
    // POSIX では符号付き整数であることが保証される。
    if (time(&now) == -1) {
        puts("Failed to get time!!");
        return -1;
    }
#endif
    // gettimeofday() や clock_gettime() を使うと、マイクロ秒やナノ秒単位で時刻を求めることができる。
#if 0
    // timezone 構造体は廃止予定の非推奨機能。
    // gettimeofday() 関数は廃止予定の非推奨機能。
    struct timeval tvo = {};
    //struct timezone tzo = {};
    //gettimeofday(&tvo, &tzo);
    gettimeofday(&tvo, nullptr);
    const time_t now = tvo.tv_sec;
#endif

    struct timespec tso = {};
    if (clock_gettime(CLOCK_REALTIME, &tso) != 0) {
        puts("Failed to get clock time!!");
        return -1;
    }
    const time_t now = tso.tv_sec;

    // ctime_r() や asctime_r() は出力フォーマットがお粗末なので却下。
    struct tm tmo = {};
    if (localtime_r(&now, &tmo) != nullptr) {
        // tm_gmtoff, tm_zone は BSD 拡張。
        const int gmtOffsetHourPart = tmo.tm_gmtoff / 3600;
        const int gmtOffsetMinPart = abs(tmo.tm_gmtoff / 60) % 60;
        // ミリ秒部分を単純に "%03.0f" で四捨五入すると、例えば 999.9 の繰り上がりで破たんする。
        //const int msPart = static_cast<int>(tvo.tv_usec / 1000);
        const int msPart = static_cast<int>(tso.tv_nsec / 1000L / 1000L);
        printf("Local = %04d-%02d-%02d %02d:%02d:%02d.%03d%+03d:%02d\n",
            tmo.tm_year + 1900, tmo.tm_mon + 1, tmo.tm_mday, tmo.tm_hour, tmo.tm_min, tmo.tm_sec,
            msPart,
            gmtOffsetHourPart, gmtOffsetMinPart);
        // DST = Daylight Saving Time: 夏時間(サマータイム)。
        printf("WeekDay = %d, YearDay = %d, DST = %d, Zone = \"%s\"\n",
            tmo.tm_wday, tmo.tm_yday, tmo.tm_isdst, tmo.tm_zone);
        // +hhmm や -hhmm 形式でよければ strftime() を使うこともできる。
        char buf[128] = {};
        strftime(buf, sizeof(buf), "%F %T %z (%Z)", &tmo);
        printf("Local = %s\n", buf);
    }

    if (gmtime_r(&now, &tmo) != nullptr) {
        printf("UTC   = %04d-%02d-%02d %02d:%02d:%02d\n",
            tmo.tm_year + 1900, tmo.tm_mon + 1, tmo.tm_mday, tmo.tm_hour, tmo.tm_min, tmo.tm_sec);
    }
}

Win32

Visual C++のランタイムライブラリには、POSIXlocaltime_r(), gmtime_r()に相当するlocaltime_s(), gmtime_s()がMSVC 8.0以降のSecure CRTとして実装されていますが、引数や戻り値の仕様が異なるので注意が必要です。また、C11規格(C++11ではない)ではlocaltime_s(), gmtime_s()が追加されていますが、MSVCの同名関数とはインターフェイスが異なるので注意が必要です。ちなみに、MSVC 8.0以降のtime_tは64bit化されていて、32bitプラットフォームでも既定で64bit整数となり、2038年問題を回避できるようになっています*3

いずれにせよ、Windowsにはgettimeofday()clock_gettime()が実装されておらず、tm構造体のtm_gmtoff, tm_zoneメンバーも実装されていないので、ミリ秒単位で時刻を求めたり、タイムゾーン情報を取得したりする場合はWindows APIを直接叩くのがベストだと思います。

なお、TIME_ZONE_INFORMATION構造体のメンバーのうち、夏時間の扱いはよく分かりませんでした。日本はサマータイムとかいうクソシステムなんぞ採用していないのに、DaylightBias-60となります。

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <Windows.h>

int main() {
    setlocale(LC_ALL, "");

    SYSTEMTIME loc = {};
    ::GetLocalTime(&loc);
    SYSTEMTIME utc = {};
    ::GetSystemTime(&utc);

    TIME_ZONE_INFORMATION tzInfo = {};
    if (::GetTimeZoneInformation(&tzInfo) != TIME_ZONE_ID_INVALID) {
        const int gmtOffsetHourPart = -tzInfo.Bias / 60;
        const int gmtOffsetMinPart = abs(tzInfo.Bias) % 60;
        printf("Local = %04hd-%02hd-%02hd %02hd:%02hd:%02hd.%03hd%+03d:%02d\n",
            loc.wYear, loc.wMonth, loc.wDay, loc.wHour, loc.wMinute, loc.wSecond,
            loc.wMilliseconds,
            gmtOffsetHourPart, gmtOffsetMinPart);
        printf("WeekDay = %hd, Zone = \"%S\"\n", loc.wDayOfWeek, tzInfo.StandardName);
    }
    printf("UTC   = %04hd-%02hd-%02hd %02hd:%02hd:%02hd.%03hd\n",
        utc.wYear, utc.wMonth, utc.wDay, utc.wHour, utc.wMinute, utc.wSecond,
        utc.wMilliseconds);
}

説明の簡略化のためprintf系を使いましたが、C/C++で文字列バッファにフォーマット出力するときはsnprintf系やstd::stringstreamを使います。もしWindows限定でよければATL::CString::Format()を使うと簡単です。

Boost C++ライブラリのboost::posix_timeboost::date_timeを使えば、一応プラットフォーム非依存なコードを書けそうですが、JavaC#のように言語標準の方法で手軽かつ簡単に、というわけにはいきません。

ところで、C++11では時刻を扱うstd::chronoが追加されましたが、このライブラリは設計が大仰なわりに機能が貧弱で、しかも使い方が直感的でなくかなり面倒です。C++20で日付フォーマット関連の機能が追加されるらしいのですが、今更ですね。標準化が遅すぎる。C/C++JavaC#に比べて言語機能もライブラリも10年どころか20年以上遅れをとっています。生産性や移植性の低いC/C++は、21世紀における新時代のアプリケーション開発に使うべき言語ではありません。引退すべき言語とまでは言いませんが、今後はハードウェア層に近いバックエンドの記述などにとどめるべきです。

*1:もちろんコンプライアンス上の問題がない限り、という条件は付きます。ログにユーザーIDやパスワードを平文で記録するようなことは絶対にしてはいけません。最近の事例だとFacebookがやらかしていたことが報じられています。Facebookが数億件分のパスワードを暗号化しないままサーバーに保存していたことが明らかに - GIGAZINE

*2:システム時刻を処理時間計測に使うと、アプリケーション稼働中にシステム時刻が変更された場合に破たんするので、単調増加することが保証されるmonotonic (steady) なタイマーを使って2点間の差を計算するのがセオリーです。とはいえ、稼働中にシステム時刻の変更がないという前提であれば、システム時刻を処理時間計測に使うことはできます。

*3:逆に言うと、古いコンパイラコンパイル&ビルドされたアプリケーションは2038年問題を回避できません。

Android Javaのアサーション

昨年2018年は転職したこともあって、一番よく使った言語はJavaでした。とはいっても、ほとんどはAndroidJavaであり、サーバーサイドやデスクトップで使われている正式なJavaとはいささか性質が異なります。

JavaC/C++C#と違ってプリプロセッサをサポートしないのですが、そのせいで一番困るもののうちのひとつはアサーション(表明)です。アサーションは「契約による設計」(design by contract) を実現・実践するために有効なプログラミング手法であり、呼び出し元が保証すべき事前条件や、呼び出し先が保証すべき事後条件をコード中に記述し、条件が満たされなかった場合はプログラムを続行せずに中断します。一方、リリースビルド(本番環境)でアサーションが無効になっているときは、引数として渡した式(表明したい条件)が評価されずに実行コストゼロとなることが好ましく、そのためC/C++C#ではコンパイルオプションに応じて有効/無効が切り替わるプリプロセッサマクロやConditional属性付きメソッドによって、ゼロオーバーヘッドのアサーションを実現しています。逆に言うと、リリースビルドではアサーションの条件式は評価されないため、必ず実行されないと困るような式(システムに対して副作用のある式)は記述しないようにします。

#include <cassert>
// NDEBUG シンボルが定義されていない場合、assert マクロが有効になる。
// アサーションが有効な場合は assert マクロが実行される。無効な場合は引数の評価ごと無視される。
// 条件が満たされず、アサーションが失敗したとき、abort 関数が実行される。
static void test(const int ary[], size_t length) {
    assert(ary != nullptr);
    for (size_t i = 0; i < length; ++i) {
        printf("%d\n", ary[i]);
    }
}

C/C++標準のassert()マクロは、表明が失敗 (assertion failure) するとabort()関数でプログラムを強制終了しますが、C#Debug.Assert()メソッドは表明が失敗した場合に無視してプログラムを続行することもできます。MSVCのCRTマクロ、_ASSERT()_ASSERTE()に近い挙動です。

// DEBUG シンボルが定義されている場合、Assert メソッドが有効になる。
// アサーションが有効な場合は Assert メソッドが実行される。無効な場合は引数の評価ごと無視される。
// 条件が満たされず、アサーションが失敗したとき、エラーメッセージが表示される。
static void Test(int[] ary) {
    System.Diagnostics.Debug.Assert(ary != null);
    foreach (int x in ary) {
        System.Console.WriteLine(x);
    }
}    

Javaにはバージョン1.4で assert ステートメントが追加されたのですが、Javaアサーションコンパイルオプションではなく実行時オプションで有効/無効を切り替えます。JITコンパイラによる最適化がうまく機能すれば、無効時にはゼロコストとなることが期待できます。

// アサーションが有効な場合は assert ステートメントが実行される。無効な場合は引数の評価ごと無視される。
// 条件が満たされず、アサーションが失敗したとき、java.lang.AssertionError がスローされる。
static void test(int[] ary) {
    assert ary != null;
    for (final int x : ary) {
        System.out.printf("%d\n", x);
    }
}

上記の例に限れば、仮にリリースビルドでnullが渡されたとしても、途中でjava.lang.NullPointerExceptionがスローされることでプログラムは中断されますが、nullを有効値として許可しないメソッドであるということをコード上で明らかにするためにもアサーションは有用です。Java 7以降であれば、java.util.Objects.requireNonNull()を併用するとなお良いです。ちなみにJSR 305の@Nonnullアノテーション (javax.annotation.Nonnull) はメソッドインターフェイスのドキュメント化に役立ちますが、すべての処理系で使えるわけではないのが残念です。

Android Javaの場合

しかし、Android 4.xまでの仮想マシン Dalvik では、アサーションの有効/無効を切り替えるシステム プロパティの debug.assert は信頼性がなく(無視されることがある)、さらにAndroid 5.0以降の仮想マシン ART (Android Runtime) では未実装とのこと。
Android StudioJava言語組み込みのassert文を使おうとすると、以下のような静的チェックツール (lint) による警告が出ます。

Assertions are not checked at runtime. There are ways to request that they be used by Dalvik (`adb shell setprop debug.assert 1`), but note that this is not implemented in ART (the newer runtime), and even in Dalvik the property is ignored in many places and can not be relied upon.
Instead, perform conditional checking inside `if (BuildConfig.DEBUG) { }` blocks. That constant is a static final boolean which is true in debug builds and false in release builds, and the Java compiler completely removes all code inside the if-body from the app.
For example, you can replace `assert speed > 0` with `if (BuildConfig.DEBUG && !(speed > 0)) { throw new AssertionError() }`.
(Note: This lint check does not flag assertions purely asserting nullness or non-nullness; these are typically more intended for tools usage than runtime checks.)
https://code.google.com/p/android/issues/detail?id=65183

つまり現在のAndroidでは、Java言語組み込みのassert文はまともに使用できません。
代替として、上記 lint では java.lang.AssertionError を明示的にスローするコードを書くことが提案されていますが、正直そんなまどろっこしいことやってられません。

Android開発環境が自動生成するクラスBuildConfigのboolean定数フィールドBuildConfig.DEBUGを使って分岐すれば、確かにコンパイラ最適化によりリリースビルドではゼロコストとなることが期待できるのですが、分岐&例外スローを逐一書くのがダルいので、自分はJUnitライクのアサーションユーティリティクラスと静的メソッドを作って対処しています。個人的には分岐やメソッド呼び出しのオーバーヘッドよりも、分岐&例外スローを逐一書くことでノイズとメンテナンスコストが増大することのほうが懸念だからです。なお、表明したい式を直接記述してbooleanで受け取るのではなく、ラムダ式で表現して遅延評価することにより、リリースビルドでの実行時コストを削減する方法もありますが、ラムダ式を使わない場合と比べてやはりコード上のノイズが増えます。

import java.util.function.*;

public class MyAssert {
    public static final boolean DEBUG = true; // BuildConfig.DEBUG で初期化する方式でも OK。

    public static void assertTrue(boolean condition) {
        if (DEBUG && !condition) {
            throw new AssertionError();
        }
    }

    public static void assertTrue(BooleanSupplier expression) {
        if (DEBUG && !expression.getAsBoolean()) {
            throw new AssertionError();
        }
    }
}

// 利用例。
MyAssert.assertTrue(ary != null);
MyAssert.assertTrue(() -> ary != null);

AndroidJavaは本物のJavaではなくJavaモドキでしかない、としばしば揶揄されますが、こういった非互換な仕様や動作には実際かなりウンザリさせられます。Oracleがブチ切れて訴訟を起こしたくなる気持ちも理解できます。自分にとってC/C++の一番嫌いなところは、処理系依存の仕様や未定義動作があまりに多すぎることですが、本来のJavaC#はそういった曖昧な仕様が極力排除されていることが最大の魅力のうちのひとつとなっています。仮にもJavaを名乗るのであれば、そういった根本的な設計思想を曲げるようなことをすべきではありません。これは技術者の倫理 (engineering ethics) に関わる問題です。

C++でstaticクラス

C#には「staticメンバーだけを定義して、実体化は許可しない」ようなクラスを簡潔に定義する手段があります。

public static class MyHelper
{
  public static int Add(int x, int y)
  { return x + y; }
}

staticクラスは状態を管理しない純粋ヘルパー(アルゴリズム)を定義するのに便利です。代表的な例でいうと、System.Mathクラスが挙げられます。
なお、staticクラスからはサブクラスを派生させることもできません。

ちなみにJavaでもクラスをstaticキーワードで修飾することができますが、意味合いがまったく違います。C#のほうがキーワードに即していて直感的だとは思いますが。

C++/CLIおよびC++/CXでは以下のように書くことでC#のstaticクラス相当を記述できます。

public ref class MyHelper abstract sealed
{
public:
  static int Add(int x, int y)
  { return x + y; }
};

しかし、従来の標準C++では、staticクラスを記述する文法は直接サポートされていません*1
せいぜいできることと言えば、以下のようにデフォルトコンストラクタとデストラクタを隠ぺいして実体化を禁止するくらいしかなく、C#と比べて分かりやすさはともかく美しさに欠けます。
また、派生を禁止するにはC++11以降のfinalキーワードを使う以外ありません。

class MyHelper final
{
private:
  MyHelper() {}
  ~MyHelper() {}
public:
  static int Add(int x, int y)
  { return x + y; }
};

MSVCにはネイティブC++でもabstractキーワードを使えるように言語拡張が施されているので、abstract sealedまたはabstract finalを指定することでstaticクラスを記述することができるのですが、Clangなどでは使えません。移植性の高いコードを書きたい場合は注意が必要です。

*1:C++では必ずしもクラスに関数を定義する必要はなく、クラスに属さない名前空間レベルの関数を定義することもできるので、これまでstaticクラスのサポートが重要視されてこなかったのかもしれません。

無修正画像のリクエストを受けたとき、どう対処・回答すべきか

三次元のお話

自分には縁のない話ですが、一般論として。
恋人に「裸の自撮り写真を送ってくれ」「他の誰にも見せたりしないから」などとお願いされても、はっきりと拒絶すべきですね。そしてそんなことを臆面もなく言ってくるリテラシーの低い野郎とは即刻別れるべきです。将来表を歩けなくなってもかまわないなら話は別ですが。
つい忘れがちですが、インターネットは全世界につながっています。一見プライバシーが保護されているように見えるメールやSNSのDMも例外ではありません。いまや画像はもちろん動画でさえ、誰でも簡単にデータをアップロード・共有できますが、同時にクリックひとつで全世界に公開できてしまいます。ネット上に裸の画像をばらまかれて永久に消せない汚点を残すような、いわゆるリベンジポルノにつながるだけでなく、刑法175条のわいせつ物頒布罪(わいせつ電磁的記録記録媒体頒布罪*1)に問われる可能性もあります。

二次元のお話

では、二次元のイラストや漫画に関してはどうでしょうか?
やはり日本国内では三次元同様「無修正はわいせつ物とみなされてアウト」になります。
pixivなどのSNSサイトで、R18カテゴリーのエッチなイラストや漫画をアップロードされている方は注意が必要です(自分を含めて)。
自分で撮った写真や自分で描いた絵などを、自分だけがアクセスできる個人端末のハードディスクや光学メディアに保存すること、つまり無修正の元データの単純所持自体には法律的に問題ありません。そうでないと作品の創作や制作ができませんので。もちろん児童ポルノは除きます。児童ポルノは無修正だろうが修正済みだろうが、単純所持ですらアウトです*2児童ポルノ、ゼッタイダメ。

しかし、たとえ自作であろうとも、無修正画像を不特定多数に公開・譲渡するとなると、前述の刑法175条に抵触します。公開対象の多い少ないにかかわらず、メールやSNSのファイル添付、ストレージサービスなど、共有目的でインターネットにアップロードすること自体が危険だと考えてください*3
某ポルノサイトでは海外のサーバーに無修正データをアップロードすることで日本の法律を潜り抜けようとしていますが、限りなく黒に近いグレーだということもお忘れなく。

よって、他人から「○○の無修正画像をもらえませんか?」などというリクエストを受けても、安易に譲渡してはいけません。

個人的意見

これは絵描きとしての個人的な意見ですが、せっかく描いたものに、モザイクやぼかし、ベタ/ホワイトで修正なんてしたくないんです。同人誌を発行するときや、SNSサイトに公開するときに、ときおり修正が弱いと指摘されることがあり、しぶしぶ手直ししていますが、内心では引っかかるものがあります。別にゾーニングができていればいいじゃないか。むやみに隠そうとするから逆におかしなことになるんです。むしろ修正したほうが余計にわいせつ感が増すと感じるのは自分だけではないはず。滑稽ですね。ゾーニングができていない(年齢詐称による抜け道があり、事実上誰でも閲覧できる)ことのほうがよほど問題です。

具体的に何をどの程度修正すれば「わいせつ物」とみなされないか、に関しても、法律で明確に定められているわけではありません。警察のさじ加減ひとつで決まってしまいます。修正は出版社・印刷会社・イベント主催者や創作者の自主規制でしかありません。でも捕まりたくはないので、安全側に自主規制せざるを得ない。ジレンマですね。その自主規制も年々強くなっているようで、業界全体が萎縮しているように感じられます。昔はゴールデンタイムに放送されるアニメでも乳首くらいは余裕だった*4というのに……

*1:「記録」が連呼されていますが、どうやら誤植ではなく法律上の正式名称らしいです。ややこしいですね。

*2:非実在青少年」とやらの話は、ここではツッコミません。あしからず。

*3:たとえ個人的なバックアップ目的だとしても、クラウドサーバーに無修正画像をアップロードするのは避けるべきです。

*4:昨今の深夜アニメの謎の光や自主規制マークは、円盤の購買意欲をそそるための差別化戦略でもあるのかもしれませんが。

Windows 10 1709/1803でキャレットの点滅が止まる

Windows 10 1709 (Fall Creators Update) には、エディットボックスなどのキャレット(縦棒のカーソル)の点滅が5秒程度で止まってしまうバグがあるようです。確かキャレットはシステムタイマーでOSが点滅させているはず*1ですが、フォーカスしてから5秒くらい経過すると点滅が止まってしまいます。キャレットに独自の形状を使って自前で描画しているMIFESでも点滅が停止します。1703 (Creators Update) 以前ではこんな現象は発生していませんでした。1803にも同様のバグがあるようです。

キャレットの点滅は、現在のフォーカスがどのUI要素にあるのか、またアプリ(のUIスレッド)が生きているのかどうかを知ることのできる重要なインジケーターのひとつなので、これが止まってしまうとかなり困ります。この問題は、MFCアプリを開発していたときに気づきました。一瞬アプリ側のバグもしくは設計ミスかとも思いましたが、ありとあらゆるWin32アプリで発生するのでOS側に問題があることは確実です。なおFirefoxは自前のタイマーでキャレットを点滅させているようで、OSのバージョンに関係なく点滅は無限に続きます。OneNote 2016は30秒くらい経過すると止まりました。

しかし、あろうことか窓の杜の某ライターは、キャレットの点滅が止まってしまうのを「仕様変更」だとうそぶいています。これは仕様変更じゃなくてただのお粗末なバグです。なぜ公式発表もないのに、れっきとしたメディアのライターがいい加減なことを書くのでしょうか。

さらに無理やり点滅を継続させるサードパーティ製ツールの紹介までしていますが、こんなくだらないツール作るヒマがあったらMSに不具合報告すべきですね。なんでも日曜大工でDIYすればいいってもんじゃないです。自動車の構造や計器に安全基準不適合の不具合や設計ミスがあったらリコール対象でしょう。不具合に気付いたら勝手に自分で改造して対応したり、その場しのぎの改造方法をユーザー間で共有したりするべきではなく、社会全体のことも考えて国土交通省に報告すべきです。ちなみにWindows 10ではエンドユーザーでもフィードバックHubで不具合報告できるので、おかしな動作に気がついたら個人でもどんどん報告すべきですが、多数の意見・要望が集まらない限りMSは動こうとしません。無視・黙殺されるのがオチです。

Windows 10の品質問題

Windows 10は特に従来のWin32デスクトップまわりの品質が明らかに低下しているように思われます。1709はGetPixel/SetPixel関数のパフォーマンスデグレードもやらかしやがったので、まったく良い印象がありません。1703は2018年10月でサポート期限が切れますが、1709はスキップして1803にすることも検討しています。
それにしても今のWindowsは、すでにある機能を破壊・劣化させてまで追加する価値のある機能など、なにひとつ提供できてないと思います。むしろ余計な新機能のほうがユーザーエクスペリエンスの邪魔をしているくらいです。Windows 7/8.1同様の快適性を得るために、いったいいくつの機能を無効化したか知れません。既存機能の破壊と劣化はApple同様、リリーススケジュールばかり優先して品質確保をおろそかにしているツケがまわってきているだけですが、頻繁な大型アップデートによる新機能追加なぞまったく望んでもいないユーザーが、なぜそこで不利益を被らなければならないのか。極めて疑問です。

*1:点滅間隔はOSのシステム設定で変更できるほか、SetCaretBlinkTime() APIが用意されています。