How to setup a global container (C++03)?

▼魔方 西西 提交于 2019-12-03 16:31:35

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.

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.

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!