6

I am long in Java and relatively new to C++. So in Java if there is some complex static object at class level (well in Java everything is at class level), we could simply use a static block to initialize it. E.g.

public class MyClass extends MyBase {

    public static final Map<String, AComplexClass> STATIC_MAP = new HashMap<String, AComplexClass> ();

    static {
        AComplexClass first = ComplexClassFactory.createComplexType1();
        first.configure("Something", "Something");

        STATIC_MAP.put("key1", first);

        // do some more
    }
}

However given the limitation for initializing C++ static variables, this might be too complex if I wanna define the same STATIC_MAP, which seems I need to have named variables in the .cpp file for each AComplexClass object and then put to the map, creating quite some rubbish in the code and also chance for other classes to refer to them accidentally.

So what's the best practice for C++ equivilent of such static initialization logic? I assume this is the same strategy to use but maybe some other style.

innoSPG
  • 4,588
  • 1
  • 29
  • 42
Alex Suo
  • 2,977
  • 1
  • 14
  • 22
  • 3
    *However given the limitation for initializing C++ static variables,* What limitation are you talking about? – R Sahu Jul 31 '15 at 02:45
  • 1
    Note that any statics initialized will be done before main() is run. If there is a bug or a race condition, it can be quite difficult to track down. – cup Jul 31 '15 at 03:07
  • 1
    Just use inversion of control and let your application lifecycle manage the creation and destruction of the complex object. This can be applied to java and c++ and you don't need to change patterns between both languages and have better testability. – SpaceTrucker Jul 31 '15 at 06:39

6 Answers6

6

Static initialization in .cpp file

You can do something similar in C++ but you need to do your initialization in your .cpp file. You can defer initialization to a function so that you can do whatever initialization you want:

MyClass.h

#pragma once
#include <unordered_map>
#include <string>
#include "AComplexClass.h"

using MyMap = std::unordered_map<std::string, AComplexClass>;

class MyClass {
public:
  const static MyMap STATIC_MAP;
};

MyClass.cpp

#include "MyClass.h"

namespace {    
  MyMap createMap() {
    MyMap map;
    auto first = createComplexType1();
    first.configure("Something", "Something");

    map.emplace("key1", std::move(first));

    // do some more

    return map;
  }
}

const MyMap MyClass::STATIC_MAP = createMap();

main.cpp

#include "MyClass.h"

int main() {
  auto object = MyClass::STATIC_MAP.at("key1");
}

Because operator[] requires non-const access I've used at() instead.

One thing you do need to be careful of is the static initialization order fiasco which basically means you should avoid a depedency on any static initialization in another .cpp file.

Static local variable

One way of avoiding the static initialization order fiasco is to make your map a static local variable in a function. This also has the benefit that you can potentially define everything in the .h file if you want to:

MyClass.h

#pragma once
#include <unordered_map>
#include <string>
#include "AComplexClass.h"

using MyMap = std::unordered_map<std::string, AComplexClass>;

MyMap createMap() {
  // as before ...
}

class MyClass {
public:
  static const AComplexClass& STATIC_MAP(const std::string& key) {
    const static MyMap map = createMap();

    // Could return the map by reference but may as well use it in this
    // function to make the syntax slightly simpler for the calling code.
    return map.at(key);
  }
};

main.cpp

#include "MyClass.h"

int main() {
  auto object = MyClass::STATIC_MAP("key1");
}

The static local variable will be initialized when it is first used.

Chris Drew
  • 14,926
  • 3
  • 34
  • 54
3

This is something of a matter of organization. I'm not sure there is one single "best practice" everyone is going to agree on, but this is how I'd approach what you're trying to do.

First, the line

public static final Map<String, AComplexClass> STATIC_MAP = new HashMap<String, AComplexClass> ();

You can do this in C++, but you need to break it up. Assuming your class is implemented in the standard .h(pp) / .cpp divide, you'd want:

.h(pp): static Map<String, AComplexClass> STATIC_MAP;

.cpp: Map<String, AComplexClass> MyClass::STATIC_MAP = new HashMap<String, AComplexClass> ();

That will create the memory you want to use for the static object. However, you can only really do that using that syntax - no setup (and I am not aware of how to make that work if the constructor requires arguments - unless they're constants, I think you'd need a different approach). For the setup, you have two options.

Option 1: Stick it in the constructor

If you add another static variable to the class, for instance static bool firstObject / bool MyClass::firstObject = true;, then you can put something to the effect of:

MyClass::MyClass()
{
    if( firstObject )
    {
        // Do your setup
         firstObject = false;
    }
}

This introduces some overhead, but it's pretty minimal unless you're creating an insane number of these objects.

Option 2: A factory / friend class to set up the variable

If you have another class which is clearly responsible for the creation of your class (such as a factory pattern), you could make that class a friend and have it set up the values. This presumes, however, that you only have one such factory per class - although that's normally the case.

As you have it, you wouldn't need the friending bit to use this option - but I'd say that in general, you probably don't want non-const statics to be public. That is a recipe for untraceable chaos, as anything could modify them anywhere in the program.

  • 1
    "However, you can only really do that using that syntax - no setup (and I am not aware of how to make that work if the constructor requires arguments - unless they're constants" You can do whatever setup you like if you defer to a function. – Chris Drew Jul 31 '15 at 06:08
  • @ChrisDrew I agree, that probably is a solution more in line with what Alex Suo is asking - I hadn't considered doing that before. –  Jul 31 '15 at 07:12
3

The user4581301 already have the correct answer but can be improved.

In C++ you can have also members statically initialized, let's transform your Java class to a C++ one:

class MyClass : public MyBase {
public:
    static std::map<std::string, AComplexClass> STATIC_MAP;
};

That's all, no more, no less. As you can see the STATIC_MAP is part of the class but isn't initialized here so... where it should be initialized? Outside the class (it seems weird but this is the C++ way):

std::map<std::string, AComplexClass> MyClass::STATIC_MAP
{
    {"key1", ComplexClassFactory::createComplexType1("Something", "Something")},
    {"key2", ComplexClassFactory::createComplexType1("Foo",       "Bar")},
    {"key3", ComplexClassFactory::createComplexType1("Epi",       "Blas")},
    {"key4", ComplexClassFactory::createComplexType1("Mortadelo", "Filemon")},
    {"key5", ComplexClassFactory::createComplexType1("Asterix",   "Obelix")},
};

Think of it like the static block on your Java class but writted outside the class itself. The code above shows a new feature of C++, since the C++11 standard you can popullate containers using an initializer list so for the map of your class, we're passing a list of pairs of {key, value} to the std::map constructor. The key is the literal string "key?" and the value is whatever the createComplexType1 is returning.

Separating the static initialization from the definition of the class allows us to perform this initialization in the code file (cpp) instead of the header file (h), doing it have some advantages like a neater code and the possibility to hide details of the class.

The initialization and lifetime of STATIC_MAP is bound to the Static storage duration":

static storage duration. The storage for the object is allocated when the program begins and deallocated when the program ends. Only one instance of the object exists. All objects declared at namespace scope (including global namespace) have this storage duration, plus those declared with static or extern.

You can see a live demo Here.

Community
  • 1
  • 1
PaperBirdMaster
  • 12,806
  • 9
  • 48
  • 94
3

Just a remark for @ChrisDrew's answer on using an extra function to do initialization: if the function is only created for that and it's pretty tiny, it may be a good idea to use lambda function. For example i have this line of code in my .cpp when i want to initialize a static vector:

//.cpp
vector<int> MyClass::v = [](){vector<int> v; v.reserve(100); return v;}();
Leo
  • 41
  • 1
  • 6
1

I am missing one piece: making STATIC_MAP constant. You can't use operator[] on a const map because if the looked-up key doesn't exist, the map will make it. Beware. Big difference between C++ and Java maps.

#include<iostream>
#include <map>
#include <memory>

//Need a class for the example
class AComplexClass
{
public:
    virtual ~AComplexClass()
    {

    }
    // just to give it something to do. It is a complex class after all.
    virtual std::string toString() = 0;
};

// Doing this with a map<string, AComplexClass> is trivial. 
// To make things a bit nastier, let's throw in some abstraction!
class ChildOfAComplexClass: public AComplexClass
{
public:
    std::string toString()
    {
        return " Howdy! I'm a child of AComplexClass!";
    }
};

// shared_ptr will take care of the garbage collection since we're passing
// around a hierarchy of complex classes. Normally you wouldn't bother with
// a pointer and let map handle all of the allocation/deallocation, but I 
// wanted to make this example hard.

// OK. Non-trivial. 

// OK. It's still trivial.

// How much blood do you want from me?
std::shared_ptr<AComplexClass> AComplexClassFactory()
{
    return std::shared_ptr<AComplexClass>(new ChildOfAComplexClass());
}

// The main event! Declare and load up the map.
std::map<std::string, std::shared_ptr<AComplexClass>> STATIC_MAP
{
    {"one", AComplexClassFactory()},
    {"two", AComplexClassFactory()},
    {"three", AComplexClassFactory()},
    {"four", AComplexClassFactory()}
};

// And a quick test to prove it works.
int main()
{
    std::cout << STATIC_MAP["one"]->toString() << std::endl;
    return 0;
}

edit:

Chris Drew points out that std::map's at method solves the const problem AND lets the user know if a key does not exist in the map. Note that handling exceptions can be quite expensive, so I don't recommend using them for flow control. If searching for keys that are not in the map is a common use case, use map's find method.

// The main event! Declare and load up the map.
const std::map<std::string, std::shared_ptr<AComplexClass>> STATIC_MAP
{
    {"one", AComplexClassFactory()},
    {"two", AComplexClassFactory()},
    {"three", AComplexClassFactory()},
    {"four", AComplexClassFactory()}
};

// And a quick test to prove it works.
int main()
{
    try
    {
        std::cout << STATIC_MAP.at("one")->toString() << std::endl;
        std::cout << STATIC_MAP.at("five")->toString() << std::endl;
    }
    catch (std::out_of_range &oore)
    {
        std::cout << "And five does not exist in the map!" << std::endl;
    }
    return 0;
}
user4581301
  • 33,082
  • 7
  • 33
  • 54
0

Maybe it's not the best answer but it worked for my case so maybe it helps others. It is mostly based on Chris Drew's anwser (currently the accepted). In my case I actually wanted also a destructor because I'm opening a file in Chris Drew's CreateMap which of course I want to properly close again when the program is exited. Note that this answer is not fully an answer to the asked question but I think it might help other who come to this question to obtain knowledge how to deal with complex static types.

My trick is basically to create a wrapper struct/class around the type which you want to be static. This wrapper struct/class you can give an constructor and destructor. In your class where you want to have the static object you create a static object of the wrapper instead of of the type. In code this looks like:

public class MyClass extends MyBase {
  struct MyMap {
    std::unordered_map<std::string, AComplexClass> internal_map;
    MyMap(){
      auto first = createComplexType1();
      first.configure("Something", "Something");

      internal_map.emplace("key1", std::move(first));

      // do some more
    }
    ~MyMap(){
      // possible destructor
    }
  }

  public static final MyMap STATIC_MAP;
}

There might be numerous improvements to this. And maybe it's not the best answer to this question exactly but I still think it might help others coming to this question.

C. Binair
  • 419
  • 4
  • 14