0

I couldn't understand how the program below compiles successfully.

class SomeClass {
public: /** Singleton **/
    static SomeClass &instance() {
        static SomeClass singleInstance;

        return singleInstance;
    };

private: 
    SomeClass() = default;
    SomeClass(const SomeClass&) = delete;
    SomeClass &operator=(const SomeClass&) = delete;

    ~SomeClass() {}
};

int main() 
{
    SomeClass::instance();

    // SomeClass::instance().~SomeClass(); // Compiles if destructor was public

    return 0;
}
  • Who is responsible for calling the destructor of the Singleton instance?
  • How does the underlying mechanism access a private method?
Caglayan DOKME
  • 948
  • 8
  • 21
  • 1
    As far as I know, the `_init` or `_start` function (or something similar depending on your system) is responsible for that. There are no access restrictions for these functions as they are added by the compiler. Run `nm MY_EXECUTABLE` to get an idea of the name of your *init* function. – J.P.S. Mar 20 '22 at 10:21
  • Who's responsible to call `main()`? – πάντα ῥεῖ Mar 20 '22 at 10:29
  • @πάνταῥεῖ `main` is a public function and the question is about access, so your analogy isn't working. – ixSci Mar 20 '22 at 10:53
  • @ixSci I don't believe that a compilers init and teardown system for executables needs to care about _public_ access. – πάντα ῥεῖ Mar 20 '22 at 10:55
  • 2
    @πάνταῥεῖ well, that's not about what you believe it is a __different__ question the user has. And you are closing it by pointing it to a duplicate which it isn't. The question isn't about lifetime, it is obviously about __why__ it works. That probably should be somehow addressed in the Standard. – ixSci Mar 20 '22 at 10:58
  • @ixSci It works because the compiler keeps track of these, and handles destructor calls, when the program ends in a regular way. And that's what's the answer in the dupe. – πάντα ῥεῖ Mar 20 '22 at 11:05
  • @ixSci and what's wrong with the c++98 standard cites there? Are these invalidated by newer standard versions? If so, cite please, and I may be tempted to reopen the question. – πάντα ῥεῖ Mar 20 '22 at 11:14
  • @πάνταῥεῖ I don't see any mention in the answers of calling a private function by the runtime. That just as well may be ill-formed. Or not. Anyway, the answers there don't mention it. – ixSci Mar 20 '22 at 11:25
  • Long answer short, the dtor is called automatically by the C++ compiler when the instance in question reaches its end of scope. Why would you want to make the dtor private? That's like creating a locked safe with the key inside it! – kesarling He-Him Mar 20 '22 at 13:38
  • 1
    @ixSci - The compiler will have to add code to a static constructor to only run it the first time it is entered. This extra code could also call `atexit(call_destructor)`. As this is inside the constructor, it has access to everything. – BoP Mar 20 '22 at 17:20
  • @BoP that's just speculations of how it could be implemented which tells nothing about why it works. So let's say you are right, what about creating this singleton outside of its member functions? Will it work? Why? See, you need to understand if it is allowed to make any reliable claims. – ixSci Mar 20 '22 at 17:55
  • @ixSci - My claim is that code inside the constructor has access to private members. Is this not clearly stated in the standard? – BoP Mar 20 '22 at 19:07
  • @πάνταῥεῖ The question isn't about the lifetime of the corresponding variable. I'm asking how it is possible to call a private function. So, it's not duplicate. – Caglayan DOKME Mar 20 '22 at 19:33
  • @BoP You are right! It is the `atexit(..)` who calls the destructor at the very end of the program. The compiler generates an assembly in which the function pointer to the destructor is given as an argument to the `atexit(~SomeClass)`. The Modern C++ Design book of Andrei Alexandrescu mentions the details. Thanks for guiding. – Caglayan DOKME Mar 20 '22 at 20:03
  • @BoP you claim is based on a speculation (implementation of the static initialization) not C++ Standard. Again, what if the instance was created outside the member function, would it still work? That's the question which can be answered by citing the Standard not speculation why some concrete piece of code works in this concrete case on some specific compilers. As far as I'm concerned this code might be ill-formed. – ixSci Mar 21 '22 at 04:53
  • 1
    Here is the [relevant text](https://timsong-cpp.github.io/cppwp/n4868/class.dtor#15) from the C++20 draft which makes the code in question well-formed and my other example ill-formed. – ixSci Mar 21 '22 at 05:46
  • @ixSci The draft explains the situation clearly. I couldn't see your 'other example'. Am I missing something? – Caglayan DOKME Mar 21 '22 at 05:52
  • The other example I mentioned in my other comments is creating the instance outside a member function. That would be ill-formed. – ixSci Mar 21 '22 at 05:55
  • @ixSci It actually causes some other problems such as the static order initialization fiasco. I have one more closed [question](https://stackoverflow.com/questions/71494939/is-it-better-to-declare-the-static-singleton-object-outside-of-the-static-instan) on it. – Caglayan DOKME Mar 21 '22 at 05:58

1 Answers1

0

Shortly, during the construction, the compiler saves the destructor to a list of functions that will be called at the very end of the program. In the end, the list of functions, including the destructors, are called one by one and the safe destruction occurs.


Underlying Mechanism

The core of the mechanism is the exit() and atexit(..) functions provided by the C standard library. During the compilation of the program, the compiler injects some other codes around your block-scoped static variable. A pseudo representation is as below.

static SomeClass& instance() {
    static SomeClass singleInstance;

    /*** COMPILER GENERATED ***/
    // Guard variable generated by the compiler
    static bool b_isConstructed = false;

    if(!b_isConstructed)
        ConstructInstance();    // Constructs the Singleton instance
    
        b_isConstructed = true;

        // Push destructor to the list of exit functions
        atexit(~SomeClass());
    }
    /*** COMPILER GENERATED ***/

    return singleInstance;
};

The point here is to call the atexit(~SomeClass()) and register the destructor to the automatic call list of the exit() function which is implicitly called when you return from the main(..). As the function pointers are used instead of directly referring to the private destructor method, the access specifier protection mechanism is skipped.

Caglayan DOKME
  • 948
  • 8
  • 21