t.g.
t.g.

Reputation: 1749

Is it possible to construct a temporary variable in the global scope?

For an arbitrary class,

template<class T>
struct S
{
    S()
    {
        cout << "hello moto\n";
    }
};

it is possible to construct an temporary object that's destroyed immediately after its creation, but only inside functions, e.g.

void f()
{
    S<int>();
}

Moving S<int>(); outside in the global scope either does not compile in GCC, or is compiled as being a function declaration in VC++.

Is it even possible to create a global temporary object?


Edit

Although the demo code is in the form of a template class, it does have to be for my question; a plain C struct would do. But with a class it is easy to see whether this temp is actually created or not so as to be be sure this clause is not interpreted as a function declaration instead by the compiler.

Upvotes: 1

Views: 1951

Answers (3)

6502
6502

Reputation: 114461

Unfortunately pure 100% portable unnamed self-registering is not possible in C++.

The most common approach is based on the unnamed namespace:

namespace {

    class MyServiceHandler : public ServiceHandler
    {
        ...
    } my_service_handler_instance;

} // unnamed namespace

What the above code does is declaring a class without exporting its name in the global scope and then creating an instance (also with a name that is not visible in the global scope but only at the compilation unit level). With this kind of code typically the constructor of the base class will store the new instance (a service provider in this case) in some global directory so that it can be found when needed.

The C++ standard says however that the initialization of static duration objects can be "delayed" but will be performed for sure before any object from the compilation unit is used. Here comes the problem: if the static duration instance is not created then the service is not reachable from the directory and therefore no part of this module is going to be used. In other words you are guaranteed that the service registration will be done only if you access the module in some other way independently from the directory access.

These words about delayed initialization were added to leave the door open for dynamic loading of compilation units in C++ but the end result is that self registering of modules is not guaranteed to work. Note also that if no reference to any symbol of a module is done in a program then a linker could be tempted to optimize away the whole module (even if real world compilers do this a lot less frequently one would hope and if there is a special case about custom global memory allocators).

So if you are going to put your self registering code in dynamically loaded code then use whatever platform specific code is designed for initialization. If instead all your modules are fixed at compile time then just make a single module that references all of them and reference it in your main program (e.g. define a loadServices that simply initializes all modles).

If which module you need to initialize depends on the build process (i.e. on OsX you have some modules and on Windows other ones) then just use a small python or perl script in the build that will create this loader source C++ code. Just adding the modules to the link command is not guaranteed by the C++ standard to work.

Explicit initialization however is also in my opinion a better approach. When things start before main the whole system is in a fuzzy state. For example I never found an explicit list of what can be used during static initialization (start problem) and what can be used during destruction of static duration objects (the dual stop problem). If even such a list exists for the standard library still you will have the same problem at large for the whole program... (e.g. can you use your logging facility in those constructors? In the destructors?).

The less you do during those fuzzy start/stop phases the better (just registration in a directory is fine in my opinion, but anything that can fail at runtime is not).

With some environments even the debugger doesn't work correctly during these confused phases making debugging before the start or after the end of main even more difficult that normal C++ debugging. My suggestion is to try to avoid to need that.

Upvotes: 2

Nicol Bolas
Nicol Bolas

Reputation: 473212

C++ does not allow you to simply invoke code in the global scope. The only thing you're allowed to do at the global scope is to create and initialize variables. Therefore, the only way you're going to be able to "create a temporary at global scope" is to create some kind of variable and use its initialization to create a temporary.

In the most technical of sense, the following creates a global temporary, which is copied into a global variable:

Classname varName = Classname(...);

However, virtually every C++ compiler worth using will, through perfectly legal copy elision mechanics, distill this down to the equivalent of Classname varName(...).

You can attempt to force the compiler to do exactly what you want:

int varName = int(Classname(...));

This of course requires that Classname is a type that is explicitly convertible to int. The temporary will be created, converted to an integer, and the temporary will then be destroyed after the integer variable is set. You don't care what the value of that variable is, so you may attempt to just return 0;.

Since you're creating and destroying an object, the only possible reason to do what you're doing is if your constructor/destructor pair changes some state outside of that object. You are strongly advised to avoid this where possible. Class constructors/destructors should not reach out and poke at some global stuff, especially since that global stuff may not have been initialized yet. What you are doing is almost certainly bad form and of dubious merit.

I strongly advise against it.

I'd like to write some self register feature, which is implemented in a class ctor. The class itself is not used after the ctor.

Here is a much better way of doing what you want:

template<typename T>
class TypeRegistrar
{
  TypeRegistrar()
  {
    T var();
    //Do registration stuff with `var`
  }
};

TypeRegistrar<SomeType> reg1;
TypeRegistrar<OtherType> reg2;

The sizeof(TypeRegistrar) is sizeof(int). So worst-case: 8 bytes. I wouldn't worry about it. This is much simpler, cleaner, etc, than anything else.

Though again, it's generally not a good idea to do a lot of pre-main setup stuff.

Upvotes: 1

GManNickG
GManNickG

Reputation: 503775

To merely create? Sure:

//         vvvvvvvv creates temporary
S<int> x = S<int>();

Or to save space on the global:

char x = (S<int>(), 0); // uses comma operator

But this (ab)uses initialization. Your program starts at main, and you aren't meant to run arbitrary statements before then. You should embrace that, not try to work around it.

Upvotes: 3

Related Questions