【问题标题】:How to add constructors/destructors to an unnamed class?如何将构造函数/析构函数添加到未命名的类?
【发布时间】:2014-03-20 14:35:03
【问题描述】:

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

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

    // f implementation - usage of instances
}

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

【问题讨论】:

  • @πάνταῥεῖ 显然我需要睡觉了。
  • 这个问题显然是探索性的。我知道你不能,至少不是通常的方式。标准读取 构造函数没有名称。使用可选函数说明符 (7.1.2) 后跟构造函数的类名和参数列表的特殊声明符语法用于声明或定义构造函数 如果没有名称,则不能这样做。我感兴趣的是解决方法的存在和实际调用的机制
  • 为什么不直接命名呢?让调试更容易开始。
  • 我并不是在暗示我拒绝命名的类型(在我的代码中),也不是匿名类是我的习惯。我只是在探索对构造函数/析构函数的隐含调用的机制,并寻找在学术意义上我会非常感兴趣的解决方法。如果这个问题是我的问题,我很抱歉。
  • @NikosAthanasiou “......匿名课程也不是我的实践......” 从技术上讲,这是一个未命名的课程(如您在问题中所述),而不是“匿名课程”。允许使用未命名的类,anonymous classes are not(尽管这在 C++ 中可能会发生变化,因为它们在 C11(不是 C++11)中是允许的)。

标签: c++ class constructor destructor declaration


【解决方案1】:

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

【讨论】:

  • 我不会称之为有用的答案。它不回答任何问题。它只是说,“不,你不能在当前规范中做到这一点。”并且在实施上是错误的。从编译器的角度来看,我看不出它为什么不能为这样的构造函数提供一个自动损坏的名称。对于 lambda 函数等,它已经这样做了。所以这个解释非常没有意义。
  • @c00000fd 未命名的类由用户定义。 Lambda 由编译器定义。所以这是一个很大的区别。那是你的评论没有意义。问题是关于用户如何为未命名的类声明构造函数或析构函数。他没有这种可能。
【解决方案2】:

如果您正在考虑 C++ 名称,那么任何具有对象的类都必须具有析构函数,无论您是否显式创建它。所以是的,编译器知道如何分配名称。但是,该命名约定是否适合您的业务,可能不是。

实际上,您可以创建一个结构,也可以创建一个没有名称的命名空间。您仍然需要在某处有名称,因为在链接所有这些时,链接器需要某种名称才能使其全部工作,尽管在许多情况下它将是在编译时立即解析的本地名称 - 通过汇编器。

了解编译器分配的名称的一种方法是查看调试字符串并查看与您感兴趣的不同地址对应的内容。当您使用 -g 编译时,您应该获得所有必要的调试让你的调试器用正确的“名称”将你的当前放置在正确的位置......(我看到没有名称的名称空间它说“名称空间”,我很确定结构在更高级别使用相同的技巧.)

【讨论】:

    【解决方案3】:

    最简单的解决方案是将命名的结构实例作为成员放入未命名的实例中,并将所有功能放入命名实例中。这大概是唯一兼容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 值初始化。您可以使用 lambda 调用的结果来初始化它们,因此您可以在初始化期间真正执行任意复杂的代码。

    #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 调用移动到辅助类,但空类仍会占用未命名结构中的一些空间(如果它是最后一个成员,通常是一个字节)。

    #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 实例中的数据冗余。存储实例的位置与未命名结构的头部有一个固定的偏移量。您可以获得这样的偏移量并将其包装在一个类型(see here)中。这样我们就可以去掉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 可能不会产生任何开销。

    【讨论】:

    • 非常有趣的方法!尤其是用 lambda 构造的 char。注意 VC++,它会抱怨将 0 转换为 char(因为返回不是明确的 char)。也许使用 bool 更干净?
    • 在我看来,值初始化技巧不适用于未命名的 class/struct 基本类型。它也不允许向成员提供(合理的)构造参数。不过还是很高兴知道!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-24
    相关资源
    最近更新 更多