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

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

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キーワードで修飾することができますが、意味合いがまったく違います*1C#のほうがキーワードに即していて直感的だとは思いますが。

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クラスを記述する文法は直接サポートされていません*2
せいぜいできることと言えば、以下のようにデフォルトコンストラクタとデストラクタを隠ぺいして実体化を禁止するくらいしかなく、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とするのがセオリーです。

// 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-07追記:
C++17以前では言語仕様にお粗末なバグがあり、コンストラクタをdelete指定しているにもかかわらず集成体初期化による実体化ができてしまいます。前述のようにデストラクタもdelete指定しておけば、少なくとも削除はできなくなるので大抵のケースでは実質的に実体化を禁止できますが、さらにコンストラクタをexplicit指定しておけばC++17以前でも確実に実体化を禁止できます。C++20以降ではこの仕様バグが修正されています。

class MyHelper final
{
public:
  explicit MyHelper() = delete;
...
};

*1:Javaのクラスに対するstatic指定子は、入れ子になったネストクラスにのみ適用可能です。staticを指定しないネストクラスは外部クラスのインスタンスを暗黙的にキャプチャするインナークラス(内部クラス)となり、メソッド内で定義可能な匿名クラスやローカルクラスと似たようなクロージャ特性を持ちます。staticを指定したネストクラスは外部クラスのインスタンスをキャプチャしない静的クラスとなり、C#のネストクラスに近くなりますが、ネストクラスのメンバーに対するアクセス指定子の扱いがC#とは異なります。個人的にJavaのネストクラスは内部クラス/静的クラス問わず直感性に欠け、落とし穴の多い欠陥機能だと思っています。Nested Classes (The Java™ Tutorials > Learning the Java Language > Classes and Objects)

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