0

I have multiple global object (say, Duck, Dog, Cat) which has to be global object, and they have a common parent class Animal.

I try to use registry pattern in such way:

  1. declare all of these instances in global object in .cc file, like Cat cat("cat");
  2. The ctor of all derived animals are empty, so it directly delegates to parent class's ctor. In the ctor of Animal, it register itself into a single registry by AnimalRegistry::GetInstance().Register(this);
  3. AnimalRegistry maintains a "animal-type -> animal instance*" mapping

When I'm using thread-sanitizer, I meet an issue: ThreadSanitizer: data race on vptr (ctor/dtor vs virtual call)

After some searching, I think the problem could be: https://github.com/google/sanitizers/wiki/ThreadSanitizerPopularDataRaces#data-race-on-vptr-during-construction

Since these registration happens in constructor, and at this time the objects are partially constructed.

My question is: how could I achieve global objects registration?

Tinyden
  • 524
  • 4
  • 13
  • 1
    Hard to tell what is happening as you have not shown us code. The problem is that English is not a very exact language (hence code is not written in English but in a programming language). Your description seem reasonable, but not exact enough to give us the detail we need to make an absolute determination of the issue. Need to see The Animal constructor. The register function and any other functions that are called. Preferably, you would write a minimal compilable example of the problem and give us instructions to reproduce the issue. – Martin York Apr 18 '22 at 06:24
  • Hi @MartinYork thank you for reply. But the source code is internal, I cannot share them completely here. I tried to make some small example code (with animals) but cannot repro the error :( I could definitely put how we use it in codebase, but if it cannot be reproduced, it could be misleading :( – Tinyden Apr 18 '22 at 07:06
  • @MartinYork Oh wait, maybe a typical example could be code snippet on the thread-sanitizer doc (https://github.com/google/sanitizers/wiki/ThreadSanitizerPopularDataRaces#data-race-on-vptr-during-construction) – Tinyden Apr 18 '22 at 08:02
  • Either ignore the error (if no thread will access the registered objects at global initialization time) or move the registration elsewhere (e.g. into the constructor of the derived classes, or you initialize the derived classes with a function (which registers after constructing) instead of directly as global variables. The disadvantage is that it is not done automatically. – Sebastian Apr 18 '22 at 11:01
  • Alternatively you can alter the class relationships. Either put the former parent Animal as member variable into the formerly derived classes (probably not so suitable for your use case, as you probably use the inheritance in your registry) or use a template: `template class Animal : public SuperAnimal`. `SuperAnimal` exists for the every code (e.g. the registry), which needs inheritance and a common ancestor. `Animal<>` contains the common parts and does the registration in its constructor, `MyAnimal` modifies the behavior and can be called by `Animal<>`. – Sebastian Apr 18 '22 at 11:09
  • You can also use an `int` template parameter being the *animal id* (instead of the class template parameter) and specialize the `MyAnimal<>` class with it. E.g. `MyAnimal`. All member functions would be templates themselves. Then you can directly write template specializations of the member functions of `MyAnimal`, but with the constructor being the default one and registering the new animal in the registry (you said the other constructors were empty). – Sebastian Apr 18 '22 at 11:16
  • I think the story is: the example listed in the thread-sanitizer [document][1] could happen since partially constructed object is used. But for global instances, as long as you don't spawn threads in ctor, it's safe, and partially constructed object won't be used. [1]: https://github.com/google/sanitizers/wiki/ThreadSanitizerPopularDataRaces#data-race-on-vptr-during-construction – Tinyden Apr 20 '22 at 07:52

1 Answers1

0

The problem here is that you declared all instances "... in .cc file, like Cat cat("cat");", as you write above.

This looks okay at first glance, but consider this: Another (global) static variable starts a thread and this thread is accessing your global objects, possibly before they have been constructed.

If you can be sure that this will not happen, you can ignore it. Otherwise look at std::call_once and include std::call_once construct to all calls using your registry objects. This will make sure that regardless of what thread is using the registry object, the objects will have been constructed and their constructors will have been called exactly once.

Hajo Kirchhoff
  • 1,969
  • 8
  • 17