C#には「staticメンバーだけを定義して、実体化(インスタンス化)は許可しない」ようなクラスを簡潔に定義する手段があります。
public static class MyHelper { public static int Add(int x, int y) { return x + y; } }
staticクラスは状態を管理しない純粋ヘルパー(アルゴリズムや完全定数)を定義するのに便利です。代表的な例でいうと、System.Mathクラスが挙げられます。
なお、staticクラスは継承不可能となっており、派生クラス(サブクラス)を定義することもできません。インスタンス化できないのだから継承の仕組みを使う意義がないため、これは当たり前ですね。
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++03以前では構文上で継承を禁止することができず、継承を禁止するにはC++11以降のfinalキーワードを使う以外ありません。
// C++03 以前。 class MyHelper { private: MyHelper() {} ~MyHelper() {} public: static int Add(int x, int y) { return x + y; } };
なお、C++11以降ではデフォルトコンストラクタとデストラクタをdelete指定することで、実体化を禁止することもできます。メンバーをdelete指定する場合はprivateではなくpublicとするのがセオリーです。詳しくはClang-Tidyのmodernize-use-equals-deleteの解説を参照してください。
// C++11 以降。 class MyHelper final { public: MyHelper() = delete; ~MyHelper() = delete; public: static int Add(int x, int y) { return x + y; } };
MSVCにはネイティブC++でもabstractキーワードを使えるように言語拡張が施されているので、abstract sealedまたはabstract finalを指定することでstaticクラスを記述することができるのですが、Clangなどでは使えません。移植性の高いコードを書きたい場合は注意が必要です。MSVCではコンパイルオプション/permissive-を付けることで独自拡張を無効化することもできます。
2023-05追記:
C++17以前では言語仕様にお粗末なバグがあり、コンストラクタをdelete指定しているにもかかわらず集成体初期化による実体化ができてしまいます。前述のようにデストラクタもdelete指定しておけば、少なくとも削除はできなくなるので大抵のケースでは実質的に実体化を禁止できますが、さらにコンストラクタをexplicit指定しておけばC++17以前でも確実に実体化を禁止できます。C++20以降ではこの仕様バグが修正されています。
class MyHelper final { public: explicit MyHelper() = delete; ... };
Javaのstaticクラス
ちなみにJavaでもクラスをstaticキーワードで修飾することができますが、意味合いがまったく違います。
Javaのクラスに対するstatic指定子は、トップレベルクラスには適用不可能であり、入れ子になったネストクラスにのみ適用可能です。
- staticを指定しないネストクラスは外部クラスのインスタンスを暗黙的にキャプチャするインナークラス(内部クラス)となり、メソッド内で定義可能な匿名クラスやローカルクラスと似たようなクロージャ特性を持ちます。
- staticを指定したネストクラスは外部クラスのインスタンスをキャプチャしない静的クラスとなり、C++やC#のネストクラスに近くなりますが、ネストクラスのメンバーに対するアクセス指定子の扱いがC++やC#とは異なります。
class MyClass { private int x; private class MyInnerClass { private void doSomething() { // 暗黙的にキャプチャした外側の MyClass インスタンスのメンバーにアクセス可能。 x = 10; // 下記のように書くこともできる。 MyClass.this.x = 10; } }; private static class MyStaticClass { private void doSomething() {} }; public void doWithInnerClass() { System.out.println("x = " + this.x); // 内部クラスはコンストラクタやインスタンスメソッド内でのみ生成可能。 MyInnerClass obj = new MyInnerClass(); obj.doSomething(); // おいおい private メソッドなのにアクセスできるのかよ…… System.out.println("x = " + this.x); } public static void doWithStaticClass() { // 静的クラスはクラスイニシャライザやクラスメソッド内でも生成可能。 MyStaticClass obj = new MyStaticClass(); obj.doSomething(); // おいおい private メソッドなのにアクセスできるのかよ…… } };
明らかにC#のstaticクラスのほうがキーワードに即していて直感的ですね。
個人的にJavaのネストクラスは内部クラス/静的クラス問わず直感性に欠け、落とし穴の多い欠陥機能だと思っています。
Javaではクラスをfinal修飾することで継承を禁止しますが、インスタンス化を禁止する専用構文はないため、privateなデフォルトコンストラクタをユーザー定義するのが一般的です。やはりC#に比べるとボイラープレートコードを量産する羽目になるため、美しさや簡潔さに欠けます。