Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Templated Id Generator well defined behavior?

I'd like to avoid assigning an Id to all my Classes where an Id is needed, so I wrote a small templated id generator.

BaseIdGenerator::makeUniqueId simply returns a new Id, everytime it is called:

class BaseIdGenerator {
protected:
    static inline Id makeUniqueId() {
        static Id nextId = 0;
        return nextId++;
    }
};

For assigning an Id to individual classes, the class is simply passed as a template argument to IdGenerator:

template <typename T>
Id getId();

template <typename T>
class IdGenerator final : public BaseIdGenerator {
    static Id id;

    template <typename Type>
    friend Id getId();
};

template <typename T>
inline Id getId() {
    return IdGenerator<T>::id;
}

template <typename T>
Id IdGenerator<T>::id = IdGenerator<T>::makeUniqueId();

This will call makeUniqueId() exactly one time per class (even at multithreaded applications since C++11 due to the thread safe local static variables)

In action this looks like this:

int main() {
    std::cout << getId<int>() << std::endl;  // prints 0
    std::cout << getId<bool>() << std::endl; // prints 1
    std::cout << getId<char>() << std::endl; // prints 2
}

This works as expected.

  • Is this well defined behavior in C++?
  • Will many uses of getId() break this functionality? (Multiple source files etc.)
  • Is this behavior standardized, so on every machine the output will be the same and in a network application it will work as expected?
like image 800
Tim Diekmann Avatar asked Jun 17 '17 12:06

Tim Diekmann


2 Answers

This will call makeUniqueId() exactly one time per class (even at multithreaded applications since C++11 due to the thread safe local static variables)

The initialization of the local static variables is thread safe. Not modifying them , so just having a static variable local in a function will make sure that it is constructed once and only once in multithreaded programs. Anything else that you do manually is prone to race conditions and requires synchronization on your end. For example, what you have above is prone to race conditions if you call makeUniqueId() concurrently from multiple threads.

This wikipedia article has a good explanation of how static local variable construction works and how they it is guarded from multithreaded access. But note again that it is only the construction of the static local variable that is protected by the language and the compiler.

Is this well defined behavior in C++?

As you have it right now, assuming all your code compiles, yes it is well defined behavior, it does not violate ODR as is if that is what you are thinking. However, if you specialize the getId() and/or IdGenerator class in an implementation file and link that to another file that does not see that specialization, then you are violating ODR, since now there are two definitions of the same thing in the system (one that is specialized and one that isn't). The compiler is not required to even warn with any diagnostics in that case. So although this works as is, be careful and know about ODR. For more see http://en.cppreference.com/w/cpp/language/definition

Will many uses of getId() break this functionality? (Multiple source files etc.)

You can use getId() as many times as you want. Static variable initialization order is not specified however, so getId() may not return the same values all the time. However you will have distinct values if you don't have races and ODR violations (as mentioned above)

Is this behavior standardized, so on every machine the output will be the same and in a network application it will work as expected?

Static variable initialization order is unspecified across translation units, so I would say it might not be the same on every machine. I didn't really follow how this would change for a network application. The code to initialize the values is ran before the program starts. So all the ids will be set before any network I/O (assuming you don't have network I/O in a static function called before main() is ran, in which case one of the values for getId() might not even be initialized)

like image 160
Curious Avatar answered Sep 22 '22 00:09

Curious


  • Is this well defined behavior in C++?

Yes, it's well defined.

  • Will many uses of getId() break this functionality? (Multiple source files etc.)

No, not in general. But multithreaded access might cause race conditions.

  • Is this behavior standardized, so on every machine the output will be the same and in a network application it will work as expected?

Yes the behavior is standardized.

I don't see the interference with network application, besides endianess. Of course the resulting ID's will be in the same endianess as the host machine where they are generated. To avoid that use the htonl() function (assuming Id is a long integer type).

like image 34
πάντα ῥεῖ Avatar answered Sep 20 '22 00:09

πάντα ῥεῖ