Andrew
Andrew

Reputation: 203

Building C++ Static Array Across Multiple Source Files

I have a system that looks something like this:

Master.h

extern const Activators[2];

Master.cpp

#include <TypeOne.h>
#include <TypeTwo.h>
const Activators[2] = { &TypeOne::Create, &TypeTwo::Create };

Where you can imagine TypeOne and TypeTwo are classes with a static Create method that returns a new instance.

I'm looking for a way to decompose this system such that there doesn't need to be a single file that creates a link-time dependency on all of the types.

I'd like to be able to link together a unit test with just TypeOne's object file and a version of the static Activators array that is only filled with the function pointer to TypeOne's Create method.

Is there a way in C++ to create a statically-defined array and then fill individual slots in that array across compilation units? Ideally I'd be able to have something like this:

Master.cpp

const Activators[2];

TypeOne.cpp

Activators[0] = &TypeOne::Create;

TypeTwo.cpp

Activators[1] = &TypeTwo::Create;

Upvotes: 2

Views: 831

Answers (4)

Mooing Duck
Mooing Duck

Reputation: 66961

The way C++ initializes globals is really weird, and technically the other answers thus far have undefined behavior, though will probably work on your machine/compiler anyway. Basically, the problem is that when the program starts, the implementation is only required to initialize the globals in main.cpp and it's headers. When your code calls a function that's defined in another cpp/header combo (translation unit), only then is C++ required to initialize the globals in that one translation unit.

The easiest (safe) workaround in your particular case, is to simply do the initialization in the header. If a file has include "TypeOne.h", it will initialize Activators[0] itself. To be portable, it's important that the translation unit (cpp file) that contains int main() also includes the headers for all of these that you need to use. Otherwise, you aren't strictly guaranteed that they'll be initialized before main begins.

in TypeOne.h

#include "master.h"

class TypeOne { 
    static std::unique_ptr<TypeOne> Create();
    //stuff
};
static const auto TypeOneInitialized = Activators[0] = &TypeOne::Create;

If you have a cpp who shouldn't depend on TypeTwo, simply don't include it's header.

Upvotes: 1

Bill Lynch
Bill Lynch

Reputation: 81986

Yes. Although you need to be very careful.

TypeOne.cpp

namespace {
    class BeforeMain {
        BeforeMain() {
            Activators[0] = &TypeOne::Create;
        }
    };

    BeforeMain obj;
}

TypeTwo.cpp

namespace {
    class BeforeMain {
        BeforeMain() {
            Activators[1] = &TypeTwo::Create;
        }
    };

    BeforeMain obj;
}

Then, other than this, just don't access the array until main() is called.

Personally though, I'd rather see Activators be a std::vector<T>, and then have each BeforeMain use std::vector<T>::push_back().

Upvotes: 0

R Sahu
R Sahu

Reputation: 206717

I would provide a functional interface to add Activators and use it from TypeOne.cpp and TypeTwo.cpp.

In Activators.h:

void addActivator(Activator activator);

In Activators.cpp:

static std::vector<Activator> activators{};

void addActivator(Activator activator)
{
   activators.push_back(activator);
}

In TypeOne.cpp:

struct Initializer
{
   Initializer()
   {
      addActivator(&TypeOne::Create);
   }
};

static Initializer initializer;

In TypeTwo.cpp:

struct Initializer
{
   Initializer()
   {
      addActivator(&TypeTwo::Create);
   }
};

static Initializer initializer;

Upvotes: 0

Chad
Chad

Reputation: 19052

Assuming that Activators are a polymorhpic (where Type1 and Type2 both derive from Activators) type, I would approach the problem like this.

Activators.h

std::vector<std::unique_ptr>>& activators();

Activators.cpp

std::vector<std::unique_ptr>>& activators()
{
   static std::vector<std::unique_ptr>> the_array(2);
   return the_array;
}

Then in individual compilation units you can assign whatever you want:

Type1.cpp

#include "Activators.h"

struct setup_activator_type_1
{
    setup_activator_type_1()
    {
       activators()[0].reset(new Type1);
    }
};

static setup_activator_type_1 type1_static_initializer;

Upvotes: 0

Related Questions