如何将构造函数/析构函数添加到未命名的类?

Nik*_*iou 38 c++ constructor destructor class declaration

有没有办法在未命名的类中声明构造函数或析构函数?考虑以下

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

    // f implementation - usage of instances
}
Run Code Online (Sandbox Code Playgroud)

后续问题:实例是基于任何基于堆栈的对象构造(和销毁)的.叫什么?它是由编译器自动分配的错位名称吗?

Rei*_*ica 35

最简单的解决方案是将一个命名的struct实例作为成员放在未命名的实例中,并将所有功能放入命名实例中.这可能是与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;
}
Run Code Online (Sandbox Code Playgroud)

以下所有内容都需要C++ 11值初始化支持.

为了避免嵌套,构造的解决方案很容易.您应该对所有成员使用C++ 11值初始化.您可以使用lambda调用的结果初始化它们,这样您就可以在初始化期间真正执行任意复杂的代码.

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

您当然可以将所有"构造函数"代码推送到单独的成员:

int b { [this]{ constructor(); return 42; }() };
void constructor() {
   std::cout << "constructed" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

这仍然不能完全读取所有内容,并将初始化b与其他内容混淆.您可以将constructor调用移动到辅助类,代价是空类仍占用未命名结构中的一些空间(如果它是最后一个成员,通常是一个字节).

#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;
}
Run Code Online (Sandbox Code Playgroud)

由于c将使用一些空间的实例,我们不妨明白它,并摆脱帮助.以下是C++ 11习惯用法的气味,但由于return语句有点冗长.

struct {
   double a { sqrt(4) };
   int b { 42 };
   char constructor { [this]{
      std::cout << "constructed" << std::endl;
      return char(0);
  }() };
}
Run Code Online (Sandbox Code Playgroud)

要获取析构函数,您需要帮助程序来存储指向包装类实例的指针,以及指向调用实例上析构函数的函数的函数指针.因为我们只能在he​​lper的构造函数中访问未命名的struct类型,所以我们必须生成调用析构函数的代码.

#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;
}
Run Code Online (Sandbox Code Playgroud)

现在你肯定在抱怨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;
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,似乎无法从内部摆脱函数指针ConstructDestruct.但这并不是那么糟糕,因为它的大小必须非零.无论如何在未命名的struct之后立即存储的内容很可能与函数指针大小的倍数对齐,因此sizeof(ConstructDestruct)大于1时可能没有开销.


Vla*_*cow 12

您不能为未命名的类声明构造函数或析构函数,因为构造函数和析构函数名称需要与类名匹配.在您的示例中,未命名的类是本地的.它没有链接,因此不会创建任何损坏的名称.