48

Is there a way to declare a constructor or a destructor in an unnamed class? Consider the following

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

    // f implementation - usage of instances
}

Follow up question : The instances are ofcourse constructed (and destroyed) as any stack based object. What gets called? Is it a mangled name automatically assigned by the compiler?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • How would you name / declare them? – Nikos Athanasiou Feb 19 '14 at 23:01
  • 1
    @πάνταῥεῖ Obviously I need to get some sleep. – juanchopanza Feb 19 '14 at 23:04
  • 3
    The question is obviously explorative. I know you can't, not the usual way at least. The standard reads __Constructors do not have names. A special declarator syntax using an optional function-specifier (7.1.2) followed by the constructor's class name followed by a parameter list is used to declare or define the constructor__ When you have no name you can't do that. What I'm interested in, is the existence of workarounds and the mechanics of the actuall call – Nikos Athanasiou Feb 19 '14 at 23:08
  • 2
    Why not just name them? Makes debugging easier for a start. – Ed Heal Feb 19 '14 at 23:11
  • 2
    I'm not implying having a type (in my code) that I refuse to name, nor that anonymous classes are a practice of mine. I'm merely exploring the mechanics of the implied calls to constructor/destructor and searching for the existence of a workaround that would be of great interest to me in the academic sense. I'm sorry if the question appeared as a problem of mine. – Nikos Athanasiou Feb 19 '14 at 23:21
  • See also [this question](http://stackoverflow.com/q/32237093/1329652) (not a duplicate). – Kuba hasn't forgotten Monica Aug 27 '15 at 02:50
  • 1
    @NikosAthanasiou "...nor that anonymous classes are a practice of mine..." Technically this is an unnamed class (as you stated in the question), not an "*anonymous class*". Unnamed classes are allowed, [anonymous classes are not](https://stackoverflow.com/questions/2253878/why-does-c-disallow-anonymous-structs) (though that may change in C++, as they are allowed in C11 (not C++11)). – monkey0506 May 31 '18 at 06:59
  • @monkey0506 Thank you for your comment (unfortunately I can't edit my comment but I'm happy to learn this) https://media.giphy.com/media/75ZaxapnyMp2w/giphy.gif – Nikos Athanasiou May 31 '18 at 19:54
  • @NikosAthanasiou The terms are commonly used interchangeably, but especially in a language like C++, semantics matter. Googling "C++ anonymous class" almost exclusively yields results for "unnamed classes", which is why I felt prompted to comment (three years after your own comment, when I stumbled upon it!). Cheers! – monkey0506 May 31 '18 at 20:25

3 Answers3

49

The simplest solution is to put a named struct instance as a member in the unnamed one, and put all of the functionality into the named instance. This is probably the only way that is compatible with 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;
}

Everything that follows requires C++11 value initialization support.

To avoid the nesting, the solution for the construction is easy. You should be using C++11 value initialization for all members. You can initialize them with the result of a lambda call, so you can really execute arbitrarily complex code during the initialization.

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

You can of course shove all the "constructor" code to a separate member:

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

This still doesn't read all that cleanly, and conflates the initialization of b with other things. You could move the constructor call to a helper class, at the cost of the empty class still taking up a bit of space within the unnamed struct (usually one byte if it's the last member).

#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;
}

Since the instance of c will use some room, we might as well get explicit about it, and get rid of the helper. The below smells of a C++11 idiom, but is a bit verbose due to the return statement.

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

To get the destructor, you need the helper to store both the pointer to an instance of the wrapped class, and a function pointer to a function that calls the destructor on the instance. Since we only have access to the unnamed struct's type in the helper's constructor, it's there that we have to generate the code that calls the destructor.

#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;
}

Now you're certainly complaining about the redundancy of the data stored in the ConstructDestruct instance. The location where the instance is stored is at a fixed offset from the head of the unnamed struct. You can obtain such offset and wrap it in a type (see here). Thus we can get rid of the instance pointer in the 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;
}

Unfortunately, it doesn't seem possible to get rid of the function pointer from within ConstructDestruct. This isn't that bad, though, since its size needs to be non-zero. Whatever is stored immediately after the unnamed struct is likely to be aligned to a multiple of a function pointer size anyway, so there may be no overhead from the sizeof(ConstructDestruct) being larger than 1.

Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • very interesting approach! especially the `char` constructed with a lambda. note on VC++, it'll complain about casting a `0` to a `char` (since the return isn't explicitly a `char`). Perhaps using a bool is cleaner? – Elliot Woods Apr 02 '16 at 04:38
  • It seems to me that the value initialization trick will not work with the unnamed `class`/`struct` base types. Also it does not allow to provide (reasonably) construction arguments to the members. Yet still nice to know! – Adam Badura Aug 18 '16 at 23:06
18

You can not declare a constructor or destructor for an unnamed class because the constructor and destructor names need to match the class name. In your example, the unnamed class is local. It has no linkage so neither mangled name is created.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • I wouldn't call it a useful answer. It doesn't answer anything. It merely states, "No, you can't do it in the current specification." And is implementationally wrong. From the standpoint of a compiler I don't see any reason why it can't come up with an automatic mangled name for such a constructor. It already does so for lambda functions, etc. So this explanation is very pointless. – c00000fd Sep 27 '21 at 00:47
  • @c00000fd Unnamed classes are defined by users. Lambda are defined by the compilers. So it is a big difference. That is your comment does not make a sense. The question is about how the user can declare a constructor or destructor for an unnamed class. He does not have such a possibility. – Vlad from Moscow Sep 27 '21 at 07:22
2

If you are thinking of C++ names, then any class that has objects has to have a destructor whether you create it explicitly or not. So yes, the compiler knows how to assign a name. Whether that naming convention is any of your business, however, probably not.

Actually, you can create a structure or also a namespace without a name. You still need to have names somewhere because at the time you link all of that, the linker needs some kind of a name to make it all work, although in many cases it will be local names that are resolved immediately at compile time--by the assembler.

One way to know of the names assigned by the compiler is to look at the debug strings and see what corresponds to the different addresses that you're interested in. When you compile with -g then you should get all the necessary debug for your debugger to place your current at the right place with the right "names"... (I've see the namespaces without a name it says " namespace", I'm pretty sure structures use the same trick at a higher level.)

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156