2

Many C++ programmers have suffered from the fierce clashes with the global C++ objects initialization/cleanup. Eventually I've found a good enough solution to this problem, I've been using (and anjoying) it for years now. My question is: does this solution fully comply to the C++ standard, or it's "platform/implementation-dependent"?

The problem

Generally-speaking there are two major problems with global objects:

  • Unpredictable order of their construction/destruction. This bites if those objects depend on each other.
  • The construction/destruction code is executed during the CRT initialization/cleanup outside the main program entry point. There's no way to wrap this code with try/catch, or perform any preliminary initialization.

One way to overcome those issues is not using global objects at all. Instead one may use static/global pointers to those objects. During the program initialization those objects are either allocated dynamically or instantiated as automatic variables within the entry point function (main), and their pointers stored in those pointers. By such you have the full control over your "global" objects lifetime.

However this method also has some drawbacks. It's related to the fact that not only the creation/destruction of those objects is different, but also their access is different. Normally a global object resides in the data section, which is allocated by the loader, and its virtual address is known at the build time. Using global pointers leads to the following drawbacks:

  • Somewhat slower object access, extra pointer dereferencing. During the runtime instead of assuming the object is at the specified address the compiler generates the code that dereferences the global pointer.
  • Weaker optimizations. The compiler may not realize that the pointer always points to the same object.
  • If the actual objects are allocated on heap:
    • Worse performance (heap allocations are "heavy")
    • Memory fragmentation
    • Chance of out-of-memory exception
  • If the actual objects are allocated on stack (auto variables in main):
    • Stack size is usually limited. In some circumstances its consumption by "fat" objects is suboptimal.

The solution

The solution I've found is to override the object's new/delete operatiors.

// class definition
class MyObject
{
    // some members
    // ...

    static char s_pMyPlaceholder[];

public:

    // methods
    // ...

    static MyObject& Instance()
    {
        return *(MyObject*) s_pMyPlaceholder;
    }

    void* operator new (size_t) { return s_pMyPlaceholder; }
    void operator delete (void*) {}
};

// object placeholder instantiated
char MyObject::s_pMyPlaceholder[sizeof(MyObject)];

void main()
{
    // global initialization
    std::auto_ptr<MyObject> pMyObj(new MyObject);

    // run the program
    // ...

}

The trick is to allocate enough space in the global memory (by declaring a global array of the adequate size), and then use fictive memory allocation for the needed object, that will "allocate" this global memory". By such we achieve the following:

  • Semantically we allocate the object dynamically. Hence we have the full control over its lifetime.
  • Actually the object resides in the global memory. Hence all the drawbacks related to the "pointer-wise" method are inapplicable to our case.
  • The object is visible everywhere in the program. One calls MyObject::Instance() to get the reference to it. And, BTW, this function call is easily inlined by the compiler.

So that everything seems ok with this method. I'm just curious if it's legal from the C++ standard perspective.

valdo
  • 12,632
  • 2
  • 37
  • 67
  • 1
    I don't see any advantage over having a (private static) `MyObject*` member, a private constructor and an `init()` method. – Mat Aug 04 '11 at 10:53
  • @Mat: I didn't understand what you suggest. Please explain – valdo Aug 04 '11 at 11:00
  • See this question: http://stackoverflow.com/questions/1008019/c-singleton-design-pattern. The question has what I was talking about, the answers have better patterns. – Mat Aug 04 '11 at 11:02
  • This is basically a singleton, with all of its problems. Don't do that, fix your design instead. – Cat Plus Plus Aug 04 '11 at 11:04
  • @Mat: I think you've read my question **very** inattentionally, because I've explained very briefly what are the disadvantages of using "pointers" – valdo Aug 04 '11 at 11:06
  • @Cat Plus Plus: My point is exactly that I **am** using the singletones, however I (hopefully) got rid of all its disadvantages. – valdo Aug 04 '11 at 11:07
  • I read your question. I don't see what your approach provides that is better than the "pointer" approach. BTW, re-check that question I linked, the accepted answer does not have a pointer, does not do some tricky-looking casts. All your arguments about slowness of allocation, fragmentation, etc... are irrelevant to singletons that are allocated once at startup. You _might_ have a case about possible optimizations, but I won't believe that until you've proven that it actually affects you in a real-world case. – Mat Aug 04 '11 at 11:11
  • @Mat: The "accepted" answer uses the static object. It has all the disadvantages of the global object, becase **the destruction order of the global and static objects is unpredictable**. OTOH I have the full control over the lifetime of my object – valdo Aug 04 '11 at 11:24
  • @mat: **I do it when and where and how I want**. In the provided example: I initialize this object at the beginning of `main`, and destroy it at the end of `main` (it's destroyed implicitly by the d'tor of `std::auto_ptr`). IF I want I could wrap this with `try/catch`, plus if I have several such objects I will construct/destroy them in the order that I decide. – valdo Aug 04 '11 at 11:36
  • You can do that too with global pointers. – Mat Aug 04 '11 at 11:45
  • @Mat: Please decide what are you talking about: the "static singletone" (the accepted answer from the question you've mentioned), or the method with **dynamic** object allocation. In the first scenario you **may not** control your object lifespan, and in the second one you have worse runtime performance/optimization strength. – valdo Aug 04 '11 at 11:53
  • 1
    @valdo: The singleton is the biggest disadvantage of the singleton. They are very, very rarely necessary. – Cat Plus Plus Aug 04 '11 at 12:00
  • So what? Who talked about the singletone? Did I ever mention this word in the question? – valdo Aug 04 '11 at 12:18
  • @valdo: Your code is a singleton, whether you call it that, or not. – Cat Plus Plus Aug 04 '11 at 13:04
  • I used similar technique [to postpone actual creation of an object](http://codereview.stackexchange.com/questions/42248/correct-usage-of-alignas-in-stdostringstream-wrapper) – AlexT Mar 08 '14 at 16:39

3 Answers3

3

I see two problems with this, one a legality problem and one a usability problem.

The first problem is alignment: MyObject::s_pMyPlaceholder is not guaranteed to be suitably aligned to hold a MyObject.

The second problem is that you have restricted yourself to a single object of type MyObject. Create a second and you have overwritten the first with no warning.

I would suggest using boost::optional to delay initialisation of objects.

ymett
  • 2,425
  • 14
  • 22
  • Also the day you want to subclass `MyObject`, your buffer is not large enough. – Alexandre C. Aug 04 '11 at 11:25
  • I accept the remark about the memory aligned. This however may be worked around. Either by using compiler/linker-specific option (if applicable) or by allocating some reserve and aligning the memory at runtime (I bgelieve it won't be inlined/eliminated because the operand address is known at build time). About the second problem - this may be easily fixes too: Do the trick not in the object's class declaration itslef, but derive another class and do it there. – valdo Aug 04 '11 at 11:27
  • And `boost::optional` smells like a standard damn ugly "pointer-like" solution. – valdo Aug 04 '11 at 11:29
  • On the contrary `boost::optional` is definitely worth looking into, if not as a solution, as an inspiration for implementation techniques – Nicola Musatti Aug 04 '11 at 11:46
2

I don't think you have a formal guarantee that your solution works on every compliant implementation, because the C++ standard doesn't guarantee that statically allocated arrays of char are aligned as would be required by any object of the same size.

Nicola Musatti
  • 17,834
  • 2
  • 46
  • 55
  • This makes sense. This however may be worked around. Either by using compiler/linker-specific option (if applicable) or by allocating some reserve and aligning the memory at runtime (I bgelieve it won't be inlined/eliminated because the operand address is known at build time). – valdo Aug 04 '11 at 11:29
  • Absolutly. I also expect the original solution to work in most cases in practice. If I remember correctly Boost has a type_with_alignment class template, which might be useful to create a standard compliant implementation. – Nicola Musatti Aug 04 '11 at 11:42
  • You may use alignas to ensure proper alignment – AlexT Mar 08 '14 at 16:29
1

From 3.7.3.1 (Allocation functions, [basic.stc.dynamic.allocation]) (ISO/IEC 14882/2003):

2/ [...] The pointer returned shall be suitably aligned so that it can be converted to a pointer of any complete object type and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function).

My doubt is that you cannot guarantee portably that the address of s_MyPlaceHolder[0] is aligned correctly.

I don't see anything bad (in a single threaded environment) with:

#include <cstdlib>

class MyObject
{
    static MyObject* instance;

    static void release_instance() { delete instance; }

public:
    static MyObject& get_instance()
    {
        if (!instance) 
        {
            instance = new MyObject();
            std::atexit(&release_instance);
        }

        return *instance;
    }
};

except that singletons and globals are usually a poor idea (they tend to marry your code with the presence of such objects, which tightens coupling between parts of your code).

Since you're interested in controlling the lifetime of the object, you can use RAII:

class MyObject
{
    MyObject() { ... }
    ~MyObject() { ... }

    // Never defined
    MyObject(const MyObject&);
    void operator=(const MyObject&);


    static MyObject* instance = 0;
    static void create_instance()
    {
        if (instance) throw std::logic_error("Instance already created");
        else instance = new MyObject();
    }

    static void release_instance() { delete instance; }

public:
    struct InstanceKeeper
    {
        InstanceKeeper(InstanceKeeper& x) : must_clean(x.must_clean)
        { x.must_clean = false; }

        ~InstanceKeeper() { if (must_clean) release_instance(); }

    private:
        friend class MyObject;
        InstanceKeeper() : must_clean(true) { create_instance(); }  

        bool must_clean;
    };

    friend struct InstanceKeeper;
    static InstanceKeeper instance_keeper() { return InstanceKeeper(); }  

    static MyObject& instance()
    {
        if (!instance) throw std::logic_error("Instance not created");
        return *instance;
    }
};

Usage:

int main()
{ 
    MyObject::InstanceKeeper k = MyObject::instance_keeper();

    MyObject::instance().do_something();
    ...

}

You can even pass the InstanceKeeper object around to functions, it has the same behavior as std::auto_ptr.

Any performance concerns that you may have are cases of premature optimization.

Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • I accept the argument about the memory alignment. But your "singletone" suggestion is simply miserable. Please forgive me for my harsh language, I'm simply tied of explaining everyone that this is not what I want. In your specific example you **don't destroy** your allocated object at all, which is yet another barbarity. And I'm also tired by the arguments about preliminary optimizations. – valdo Aug 04 '11 at 11:49
  • @valdo: let me update about the destruction (come back in 2 minutes). – Alexandre C. Aug 04 '11 at 11:52
  • @valdo: Your operator new hack will only allocate one object before running into trouble, so you may as well implement a singleton. Using global pointers to objects or using global objects have exactly the same problems to me: they make your code suck. Static initialization order problems can be overcome by eg. what I show you, but that's all it is. And yes, without a real world program and profiler output, I will bitch about premature optimization and how your argument about performance is pointless. – Alexandre C. Aug 04 '11 at 11:53
  • @valdo: Also, if you work with any sensible C++ programmer, he will track you down and shoot you if he sees that you're overloading member `operator new`. Especially this way. Also, there is no guarantee that the `size_t` which is passed as an argument to `operator new` is `sizeof(MyObject)`. – Alexandre C. Aug 04 '11 at 11:57
  • 1. I have absolutely no problem with the fact that I may create only one such an object before "running into trouble". 2. Your example (a) won't work correctly in MT environment, (b) **never** destroys the allocated object, (c) not what I need anyway. **I don't need the object to be created on-demand**. I just want to have the full control over its lifetime. I.e. create and destroy it when and where and how I want. And about performance - I would agree with you about "premature optimization" if there was some trade-off. If there are no drawbacks - why not do this anyway? – valdo Aug 04 '11 at 12:15
  • C: sorry, I take back my claim that your method never destroys the object. It does. But it will be destroyed in the unpredictable order anyway – valdo Aug 04 '11 at 12:20
  • @valdo: About your point (a): this is a *very* subtle issue, which is best answered with C++0x and static objects (there are guarantees). Otherwise, you should read Alexandrescu's *Modern C++ design* which discusses this issues for 20 pages. So let's forget it. About (b): now it does, about (c): this is not very clear from your question that this point is the most important. Let me correct. About premature optimization: yes, there is a trade off: ugly member operator new vs readable code. I maintain that member operator new into a static buffer is premature optimization. Don't do it. – Alexandre C. Aug 04 '11 at 12:21
  • @valdo: I'm really serious about multithreading issues. As to C++03, there is no way to ensure proper behaviour (it involves subtle undefined behaviour issues with calling `atexit` inside a function registered with `atexit`) without protecting every access to the instance with a lock. This is not a concern when the singleton is a logger object which only does IO (the only *tolerable* use of a singleton). – Alexandre C. Aug 04 '11 at 12:35
  • @valdo: Please see my edits for new code. I will **never** advocate using such a scheme. It is even uglier than singletons. But *caveat emptor*. – Alexandre C. Aug 04 '11 at 12:40