Вопрос по c++, class, declaration, constructor, destructor – Как добавить конструкторы / деструкторы в безымянный класс?

36

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

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

    // f implementation - usage of instances
}

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

Вопрос явно исследовательский. Я знаю, что вы не можете, по крайней мере, не обычным способом. Стандарт читаетКонструкторы не имеют имен. Специальный синтаксис объявления с использованием необязательного спецификатора функции (7.1.2), за которым следует имя класса конструктора, за которым следует список параметров, используется для объявления или определения конструктора. Когда у тебя нет имени, ты не можешь этого сделать. Что меня интересует, так это наличие обходных путей и механизм актуального вызова Nikos Athanasiou
@NikosAthanasiou "... ни то, что анонимные классы - это моя практика ..." Технически это неназванный класс (как вы указали в вопросе), а не "анонимный класс". Неназванные занятия разрешены,анонимные занятия не (хотя это может измениться в C ++, так как они разрешены в C11 (не в C ++ 11)). monkey0506
@NikosAthanasiou Термины обычно используются взаимозаменяемо, но особенно в таком языке, как C ++, семантика имеет значение. Поиск в Google «анонимного класса C ++» почти исключительно дает результаты для «неназванных классов», поэтому я чувствовал побуждение комментировать (через три года после вашего собственного комментария, когда я наткнулся на него!). Ура! monkey0506
Я не подразумеваю наличие типа (в моем коде), который я отказываюсь назвать, или что анонимные классы - моя практика. Я просто исследую механику подразумеваемых вызовов конструктора / деструктора и ищу существование обходного пути, который был бы очень интересен для меня в академическом смысле. Я извиняюсь, если вопрос возник как моя проблема. Nikos Athanasiou

Ваш Ответ

3   ответа
30

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

#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.

Мне кажется, что трюк инициализации значения не будет работать с неназваннымclass/struct базовые типы. Также это не позволяет предоставлять (разумно) аргументы конструкции членам. И все же приятно знать! Adam Badura
очень интересный подход! особенноchar построенный с лямбда. обратите внимание на VC ++, он будет жаловаться на приведение0 кchar (поскольку возвращение явно неchar). Возможно, использование bool чище? Elliot Woods
Все это довольно бессмысленно в любом случае ... Kuba Ober
2

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

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

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

13

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

Похожие вопросы