Подтвердить что ты не робот

Как добавить конструкторы/деструкторы в неназванный класс?

Есть ли способ объявить конструктор или деструктор в неназванный класс? Рассмотрим следующее

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

    // f implementation - usage of instances
}

Следующий вопрос: экземпляры, как правило, создаются (и уничтожаются) как любой объект на основе стека. Что называется? Это искусственное имя, автоматически назначенное компилятором?

4b9b3361

Ответ 1

Вы не можете объявить конструктор или деструктор для неназванного класса, потому что имена конструктора и деструктора должны соответствовать имени класса. В вашем примере неназванный класс является локальным. У него нет привязки, поэтому не создано ни одного искаженного имени.

Ответ 2

Самое простое решение - поместить именованный экземпляр структуры как член в неназванный, и поместить все функциональные возможности в именованный экземпляр. Вероятно, это единственный способ, совместимый с С++ 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;
}

Все, что следует, требует поддержки инициализации значения С++ 11.

Чтобы избежать гнездования, решение для конструкции легко. Вы должны использовать инициализацию значения С++ 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 в класс-помощник ценой пустого класса, по-прежнему занимающего немного места внутри неназванной структуры (обычно один байт, если это последний элемент).

#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 будет использовать какую-то комнату, мы могли бы также получить отчет об этом и избавиться от помощника. Ниже пахнет идиомой С++ 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.

Ответ 3

Если вы думаете о именах С++, то любой класс, у которого есть объекты, должен иметь деструктор, независимо от того, создаете ли вы его явно или нет. Итак, да, компилятор знает, как назначить имя. Однако, если это соглашение об именах является вашим бизнесом, возможно, нет.

Собственно, вы можете создать структуру или пространство имен без имени. Вы все равно должны иметь имена где-то, потому что в то время, когда вы связываете все это, компоновщику требуется какое-то имя, чтобы все это работало, хотя во многих случаях это будут локальные имена, которые решаются немедленно во время компиляции ассемблер.

Один из способов узнать имена, назначенные компилятором, - это посмотреть строки отладки и посмотреть, что соответствует различным адресам, которые вас интересуют. Когда вы скомпилируете с -g, вы должны получить всю необходимую отладку для вашего отладчика, чтобы разместить ваше текущее в нужном месте с правильными "именами"... (Я вижу пространства имен без имени, которое он говорит "пространство имен", я уверен, что структуры используют один и тот же трюк на более высоком уровне.)