web-dev-qa-db-ja.com

名前のないクラスにコンストラクタ/デストラクタを追加するにはどうすればよいですか?

名前のないクラスでコンストラクタまたはデストラクタを宣言する方法はありますか?以下を検討してください

void f()
{
    struct {
        // some implementation
    } inst1, inst2;

    // f implementation - usage of instances
}

フォローアップの質問:インスタンスはもちろん、スタックベースのオブジェクトとして構築(および破棄)されます。何が呼ばれますか?コンパイラによって自動的に割り当てられたマングル名ですか?

39

名前のないクラスのコンストラクタまたはデストラクタを宣言することはできません。コンストラクタとデストラクタの名前はクラス名と一致する必要があるためです。あなたの例では、名前のないクラスはローカルです。リンケージがないため、マングル名も作成されません。

13

最も簡単な解決策は、名前付き構造体インスタンスを名前なしインスタンスのメンバーとして配置し、すべての機能を名前付きインスタンスに配置することです。これはおそらく、C++ 98と互換性のある唯一の方法です。

_#include <iostream>
#include <cmath>
int main() {
   struct {
      struct S {
         double a;
         int b;
         S() : a(sqrt(4)), b(42) { std::cout << "constructed" << std::endl; }
         ~S() { std::cout << "destructed" << std::endl; }
      } s;
   } instance1, instance2;
   std::cout << "body" << std::endl;
}
_

以下はすべてC++ 11値の初期化サポートを必要とします。

ネストを回避するために、構築のソリューションは簡単です。すべてのメンバーに対してC++ 11値の初期化を使用する必要があります。ラムダ呼び出しの結果でそれらを初期化できるため、初期化中に実際に任意の複雑なコードを実行できます。

_#include <iostream>
#include <cmath>
int main() {
   struct {
      double a { sqrt(4) };
      int b { []{
            std::cout << "constructed" << std::endl;
            return 42; }()
            };
   } instance1, instance2;
}
_

もちろん、すべての「コンストラクター」コードを別のメンバーに押し込むことができます。

_int b { [this]{ constructor(); return 42; }() };
void constructor() {
   std::cout << "constructed" << std::endl;
}
_

これはまだそれらすべてをきれいに読んでおらず、bの初期化を他のもので展開しています。 constructor呼び出しをヘルパークラスに移動することもできますが、名前のない構造体(通常、最後のメンバーの場合は1バイト)内で少しのスペースを空のクラスが占有します。

_#include <iostream>
#include <cmath>
struct Construct {
   template <typename T> Construct(T* instance) {
      instance->constructor();
   }
};

int main() {
   struct {
      double a { sqrt(4) };
      int b { 42 };
      Construct c { this };
      void constructor() {
         std::cout << "constructed" << std::endl;
      }
   } instance1, instance2;
}
_

cのインスタンスはいくつかの部屋を使用するため、それについて明示的に取得し、ヘルパーを削除することもできます。以下はC++ 11イディオムのにおいがしますが、returnステートメントのために少し冗長です。

_struct {
   double a { sqrt(4) };
   int b { 42 };
   char constructor { [this]{
      std::cout << "constructed" << std::endl;
      return char(0);
  }() };
}
_

デストラクタを取得するには、ラップされたクラスのインスタンスへのポインタと、インスタンスのデストラクタを呼び出す関数への関数ポインタの両方を格納するヘルパーが必要です。ヘルパーのコンストラクターでは名前のない構造体の型にしかアクセスできないため、デストラクターを呼び出すコードを生成する必要があります。

_#include <iostream>
#include <cmath>
struct ConstructDestruct {
   void * m_instance;
   void (*m_destructor)(void*);
   template <typename T> ConstructDestruct(T* instance) :
      m_instance(instance),
      m_destructor(+[](void* obj){ static_cast<T*>(obj)->destructor(); })
   {
      instance->constructor();
   }
   ~ConstructDestruct() {
      m_destructor(m_instance);
   }
};

int main() {
   struct {
      double a { sqrt(4) };
      int b { 42 };
      ConstructDestruct cd { this };

      void constructor() {
         std::cout << "constructed" << std::endl;
      }
      void destructor() {
         std::cout << "destructed" << std::endl;
      }
   } instance1, instance2;
   std::cout << "body" << std::endl;
}
_

これで、ConstructDestructインスタンスに格納されているデータの冗長性について不満を抱いているはずです。インスタンスが格納される場所は、名前のない構造体の先頭からの固定オフセットです。このようなオフセットを取得して、タイプでラップすることができます( ここを参照 )。したがって、ConstructorDestructorのインスタンスポインタを取り除くことができます。

_#include <iostream>
#include <cmath>
#include <cstddef>

template <std::ptrdiff_t> struct MInt {};

struct ConstructDestruct {
   void (*m_destructor)(ConstructDestruct*);
   template <typename T, std::ptrdiff_t offset>
   ConstructDestruct(T* instance, MInt<offset>) :
      m_destructor(+[](ConstructDestruct* self){
         reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(self) - offset)->destructor();
      })
   {
      instance->constructor();
   }
   ~ConstructDestruct() {
      m_destructor(this);
   }
};
#define offset_to(member)\
   (MInt<offsetof(std::remove_reference<decltype(*this)>::type, member)>())

int main() {
   struct {
      double a { sqrt(4) };
      int b { 42 };
      ConstructDestruct cd { this, offset_to(cd) };
      void constructor() {
         std::cout << "constructed " << std::hex << (void*)this << std::endl;
      }
      void destructor() {
         std::cout << "destructed " << std::hex << (void*)this << std::endl;
      }
   } instance1, instance2;
   std::cout << "body" << std::endl;
}
_

残念ながら、ConstructDestruct内から関数ポインターを取り除くことはできないようです。ただし、サイズをゼロ以外にする必要があるため、これはそれほど悪くはありません。名前のない構造体の直後に保存されているものはいずれにしても、関数ポインターのサイズの倍数に揃えられる可能性が高いため、sizeof(ConstructDestruct)が1より大きくなることによるオーバーヘッドはありません。

39

C++の名前を考えている場合、オブジェクトを含むクラスには、明示的に作成するかどうかにかかわらず、デストラクタが必要です。したがって、はい、コンパイラは名前を割り当てる方法を知っています。ただし、その命名規則があなたのビジネスのいずれかであるかどうかは、おそらく違います。

実際には、名前なしで構造または名前空間も作成できます。すべてをリンクするときに、リンカがすべてを機能させるには何らかの名前が必要になるため、まだどこかに名前が必要ですが、多くの場合、コンパイル時にすぐに解決されるローカル名になります-アセンブラ。

コンパイラーによって割り当てられた名前を知る1つの方法は、デバッグ文字列を調べて、関心のあるさまざまなアドレスに対応するものを確認することです。-gを使用してコンパイルすると、デバッガーに必要なすべてのデバッグを取得できます。現在の場所を適切な「名前」で適切な場所に配置するには...(名前のない名前空間は「名前空間」と表示されています。構造体がより高いレベルで同じトリックを使用していると確信しています。)

2
Alexis Wilke