2

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;
Andrew
  • 203
  • 3
  • 6
  • 1
    How is `Activator` defined? – R Sahu Jul 24 '14 at 19:36
  • Let Type*.cpp include Master.h and set each field. – erenon Jul 24 '14 at 19:36
  • Does Activators need to be `const`? – Bill Lynch Jul 24 '14 at 19:40
  • Activators doesn't need to be const. – Andrew Jul 24 '14 at 19:41
  • And for simplicity, assume Activator is defined as typedef void* (*Activator)(void); where it returns a void* to the created object. – Andrew Jul 24 '14 at 19:42
  • @Andrew: This code is missing a detail, what are the types of the elements inside the `Activators` array? Unless the elements are called `Activators` and the array is _also_ named `Activators`, which is a terrible, terrible idea. – Mooing Duck Jul 24 '14 at 20:19
  • 1
    Also, how portable does the code have to be? All of the answers here are _technically_ undefined behavior, but in practice tend to be almost completely safe. – Mooing Duck Jul 24 '14 at 20:21
  • It seem to me like what you may need is a factory pattern. E.g. in the main program, you could have a static map, which is declared extern in all other modules. When modules load, they insert themselves using their name into the map. This is a portable way to achieve more less what you want. – Jens Munk Jul 24 '14 at 21:56
  • Is `Activators` a typedef for a funtion pointer? or what – M.M Jul 24 '14 at 22:19
  • Expanding on the facotry idea: Protect it from init-order issues by having it live in a function. You have some code in TypeOne's unit call `get_factory().register(some_key, &TypeOne::Create);` , where `some_key` could be a common `enum`, or even a string. Then when it is time to create an object, the factory can gracefully deny requests for types that have not registered yet. – M.M Jul 24 '14 at 22:34

4 Answers4

1

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.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
0

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().

Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • 1
    _Technically_ this isn't portable, the globals are not guaranteed to be initialized until a function in `main.cpp`'s TU tries to call a function in each of those TUs. (This applies to all answers thus far) – Mooing Duck Jul 24 '14 at 20:18
  • @MooingDuck: Really? I didn't know that.. For anyone else reading, [here's some more information](http://stackoverflow.com/questions/1271248/c-when-and-how-are-c-global-static-constructors-called). – Bill Lynch Jul 24 '14 at 20:31
  • @MooingDuck, so have `main()` open up with `TU1dummy_func(); TU2dummy_func();` – M.M Jul 24 '14 at 22:21
0

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;
Community
  • 1
  • 1
Chad
  • 18,706
  • 4
  • 46
  • 63
  • Correct on both comments. Fixed. – Chad Jul 24 '14 at 19:42
  • Ahh this looks close to exactly what I'd like to do. Is it legal C++ to do those assignments outside of a function? – Andrew Jul 24 '14 at 19:44
  • The assignments need to happen in a function. I've updated the answer to give an idea on one way this is possible. – Chad Jul 24 '14 at 20:05
0

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;
R Sahu
  • 204,454
  • 14
  • 159
  • 270