5

I want to define a global container (C++03), and here's an example code I tried, which does not work.

#include <vector>
#include <string>
using namespace std;

vector<string> Aries;
Aries.push_back("Taurus");    // line 6

int main() {}

Compile error:

prog.cpp:6:1: error: 'Aries' does not name a type

It seems I can define an empty global vector, but cannot fill it up. Looks like in C++03, I cannot specify an initializer either, such as:

vector<string> Aries = { "Taurus" };

Have I made a mistake here, or how do I get around this problem?

I tried searching on StackOverflow to see if this has been answered before, but only came across these posts: global objects in C++, Defining global constant in C++, which did not help answer this.

Community
  • 1
  • 1
Masked Man
  • 1
  • 7
  • 40
  • 80
  • The issue is that you can't have code outside any function. Also, see http://stackoverflow.com/questions/2236197/c-easiest-way-to-initialize-an-stl-vector-with-hardcoded-elements – NPE Dec 16 '12 at 09:02

4 Answers4

12

I found a neat workaround to "initialize" C++03 global STL containers (and indeed to execute code "globally" before main()). This uses the comma operator. See example:

#include <vector>
#include <string>
#include <iostream>
using namespace std;

vector<string> Aries;

// dummy variable initialization to setup the vector.
// using comma operator here to cause code execution in global scope.
int dummy = (Aries.push_back("Taurus"), Aries.push_back("Leo"), 0);

int main() {
    cout << Aries.at(0) << endl;
    cout << Aries.at(1) << endl;
}

Output

Taurus
Leo

The only real problem, if you can call it that, is the extra global variable.

Masked Man
  • 1
  • 7
  • 40
  • 80
10

While declarations and initializations outside of a function (such as main) are no problems, you cannot have code outside of functions. You either need to set the value right at the initialization (as in C++11), or fill your global object in main:

std::vector<string> Aries;

/* ... */

int main() {
    Aries.push_back("Taurus");
    /* ... */
}

Other ways (without populating the vector in main)

Single values

There are other ways which don't need .push_back or other code in the main. For example, if Aries shall contain only one item, you can use std::vector<T>::vector(size_t s, T t) with s == 1 and t == "Taurus":

std::vector<string> Aries(1, "Taurus");

/* ... */

int main() { /* ... */ }

If you need the same value several time you can simply adjust s.

Multiple distinct value

Now this gets a little bit trickier. You want to populate the vector by using a suitable constructor. std::vector<T> provides four different constructors in C++03:

  1. std::vector<T>::vector()
  2. std::vector<T>::vector(size_t, T = T())
  3. template <class InputIt> std::vector<T>::vector(InputIt, InputIt)
  4. std::vector<T>::vector(const vector&) Note that all of them actually take an allocator as additional last parameter, but this is not of our concern.

We can forget about std::vector<T>::vector(), since we want to fill the vector with values, and also std::vector<T>::vector(size_t, T) doesn't fit, since we want distinct values. What's left is the templated constructor and the copy constructor. The following example shows how to use the templated constructor:

std::string const values[] = {"Taurus", "Ares", "Testos"};

template <class T, size_t N>
T* begin(T (&array)[N]){ // this is already in C++11, very helpful 
    return array;
}
template <class T, size_t N>
T* end(T (&array)[N]){
    return array+N;
}

std::vector<std::string> Aries(begin(values), end(values));

Note that begin and end aren't necessary - we could simple use Aries(values, values+3). However, things tend to change, and often you add a value or remove one. If you forget to change the offset 3 you would either forget an entry or cross borders of values.

However, this solution introduces a new global variable, which aren't that nice. Lets take a step back. We needed values since we wanted to use std::vector<T>::vector(InputIt, InputIt), which needs a valid range. And the range must be known at the time we use the constructor, so it needs to be global. Curses! But behold: our toolbox still contains one constructor, the copy constructor. In order to use it we create an additional function:

namespace {
    std::vector<std::string> initializer(){
        const std::string dummy_array[] = {"Taurus", "Ares", "Testos"};
        return std::vector<std::string>(begin(dummy_array), end(dummy_array));
    }
}

std::vector<std::string> Aries(initializer());

This example uses both copy constructor and range constructor. You could also create a temporary vector and use std::vector<T>::push_back to populate it in the function.

Zeta
  • 103,620
  • 13
  • 194
  • 236
  • What, not bothering to show how to set the value right at initialization in C++03? It's easily done... – Mooing Duck Feb 21 '13 at 01:35
  • @MooingDuck: Well, for a single value one could just use `std::vector::vector(size_t, T)`, but for several values we would have to use `std::vector::vector(InputIt, InputIt)` and thus an additional `const std::string []` or am I mistaken? `std::vector::vector(const &vector)` would be possible with a little helper function. – Zeta Feb 21 '13 at 13:37
  • 1
    I always go straight to the helper function in C++03: easy to understand, and handles all cases. – Mooing Duck Feb 21 '13 at 16:25
5

My experience is that this "amazing, yet terrifying" (hat tip) solution is unpredictable. Your initialization runs at load time. You have no guarantee as to load ORDER. So everything that uses that container must be either in the same module, or somehow guarantee that the module is loaded before they access the container. Otherwise, the container constructor has not run, yet your code happily calls its accessors.

When it goes wrong, it goes very wrong, and the error is compiler, platform, and phase of the moon dependent - in all cases giving not the remotest clue as to what went south.

jrw
  • 51
  • 1
  • 1
  • +1 for mentioning the issue. However, this isn't specific to this solution. – milleniumbug Feb 21 '13 at 01:21
  • BTW: The best solution to "static initialization order fiasco" is to stop using global objects with extern linkage when unnecessary. – milleniumbug Feb 21 '13 at 01:40
  • You do have a good point and I am aware of the static initialization order fiasco. However, a solution is unsafe in 90% cases doesn't mean it should not be used in the remaining 10% cases when it is safe. Given a choice between a "always safe" solution that doesn't solve the problem at hand, and a solution that's safe only 10% of the time, but solves the current problem, it is quite clear what one should do. I know you don't mean to say "never do this", but might as well post this comment for clarity of future readers. – Masked Man Mar 27 '14 at 08:00
1

And just for completeness, this solution uses iterator pair constructor and plain old array initializer.

#include <vector>
#include <string>
#include <iostream>
using namespace std;

string contents[] = {"Taurus", "Leo"};
vector<string> Aries(contents, contents + sizeof contents/sizeof contents[0]);

int main() {
    cout << Aries.at(0) << endl;
    cout << Aries.at(1) << endl;
}

This may be clearer, yet it calls more copy constructors.

milleniumbug
  • 15,379
  • 3
  • 47
  • 71
  • +1: Didn't notice your answer before I edited mine because of [Mooning Duck's comment](http://stackoverflow.com/questions/13899866/how-to-setup-a-global-container-c03/13899893#comment21058202_13899893). That could have saved me some minutes. – Zeta Feb 21 '13 at 14:04