How to mitigate user-facing API Effect of shared m

2019-01-29 14:26发布

问题:

Let's say I have a type of lookup table which I can build for a given integer:

class FooLookupTable {
    ...
public:
    FooLookupTable(int radix) { 
        ...
    }
};

Then there's a class whose template parameter is that same integer, and whose constructor initializes a member instance of this lookup table:

template <int radix> class Foo {
    ...
private:
    FooLookupTable table;
public:
    Foo () : FooLookupTable (radix) {
        ...
    }
};

Throughout my code I instantiate these with various values of radix:

 int main() {
     ...
     Foo<1> myFoo;
     Foo<1> yourFoo;
     Foo<10> theirFoo;
     ...
 }

This works and doesn't create any hairy threading or API issues. But it's not sharing the radix table for 1 between myFoo and yourFoo. I could hardcode a dependency on an assumed thread library, and build a global map that's filled on-demand. But my question is:

"In the modern C++11 world, is there a clean way designing a library for Foo which does not have dependencies outside of the standard libraries?"

I thought of using a static member for this, since each separate instantiation of a template class creates only one static member variable. But this brings up the question of who is responsible for declaring the space for the static member and whoever does so has to "know the right way to initialize it":

 FooLookupTable Foo<1>::table (1);
 FooLookupTable Foo<10>::table (10);

 int main() {
     ...
     Foo<1> myFoo;
     Foo<1> yourFoo;
     Foo<10> theirFoo;
     ...
 }

Reading what's written on the subject like " C++ Static member initalization (template fun inside) " doesn't seem to turn up much hope...unless I'm missing something. Also, what would happen if Foo instances themselves were static? :-/

回答1:

static members of templates won't work well across APIs if you want to ensure all instantiations of Foo<1> share the same table. This is because, going across module boundaries, if module A creates Foo<1> and module B creates Foo<1>, it will duplicate the static members.

I think your best bet is to map those integers to their corresponding tables at runtime.

I could hardcode a dependency on an assumed thread library, and build a global map that's filled on-demand. But my question is:

In the modern C++11 world, is there a clean way designing a library for Foo which does not have dependencies outside of the standard libraries?

As a practical answer, no, not yet. You need critical sections/mutual exclusion for your table if you want thread safety (unless you can manage to make a shared container that is thread safe and lock-free with no third party dependencies) and there's nothing out of the box in C++11 that's implemented yet (at least in popular compilers) to give you cross-platform concurrency AFAIK.

In the future, C++11's concurrency features will support mutexes and atomics out of the box, but popular compilers haven't implemented this feature yet to my knowledge.



回答2:

First, there's a standard thread library in C++11 you can rely on according to your own definition.

Then, you should mention that what you need as name: it's a singleton.

Finally, there's a way in C++11. The language is aware of threads, and as such, static variable initialization is thread safe:

template <int radix>
class Foo {
public:
     // your public stuff, no contructors

     static Foo& GetInstance()
     {
          static Foo instance(...); // thread safe accoding to C++11
          return instance;
     }

private:
     Foo(...) {
     }
}

EDIT:

You have mentioned a few fair points in the comments. Here is an answer better for your needs, I hope. Actually, the fact that FooLookup create templated foos is not useful to solve the problem. It confused me a bit, you don't need any templates for this.

// Header file
class FooLookupTable
{
public:
    // needed to use with map
    FooLookupTable()
    { }
    FooLookupTable(int index)
    { }

    static FooLookupTable& GetLookup(int index);
};


// CPP file (no library boundary problem since this file will be linked only once)

// I fear I can't do that without using a mutex explicitely, but those are in the standards.
// You'll need a recent compiler to support them (GCC 4.5 and MSVC11)
#include <mutex> 
using namespace std;

FooLookupTable::FooLookupTable& GetLookup(int index);
{
    static map<int, FooLookupTable> _tables; // thread safe in C++11
    static mutex _m;

    // The function body isn't thread safe though, so we need to protect it.
    lock_guard<mutex> l(_m); // make sure we don't create the same one twice.

    auto it = _tables.find(index);
    if (it == _tables.end()) {
        _tables[index] = FooLookupTable(index);
        return _tables[index];
    }
    else
        return *it;
}

EDIT 2

Made the function thread safe. My bad. It seems my first solution may be better for you after all. There is a solution without <mutex> if you know (at compilation time) the list of the radixes for which you want to create a lookup table.