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;
10
  • 1
    How is Activator defined? Commented Jul 24, 2014 at 19:36
  • Let Type*.cpp include Master.h and set each field. Commented Jul 24, 2014 at 19:36
  • Does Activators need to be const? Commented Jul 24, 2014 at 19:40
  • Activators doesn't need to be const. Commented Jul 24, 2014 at 19:41
  • 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. Commented Jul 24, 2014 at 20:21

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

Comments

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;

3 Comments

Correct on both comments. Fixed.
Ahh this looks close to exactly what I'd like to do. Is it legal C++ to do those assignments outside of a function?
The assignments need to happen in a function. I've updated the answer to give an idea on one way this is possible.
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;

Comments

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

3 Comments

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)
@MooingDuck: Really? I didn't know that.. For anyone else reading, here's some more information.
@MooingDuck, so have main() open up with TU1dummy_func(); TU2dummy_func();

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.