Is shared_ptr supported in class member derived from TObject?

Dear experts

I heard that ROOT doesn’t support TObject containing smart pointer saving into TFile, can someone explain the reason, or simply point me to some document? I want to know the technical reason not simply “it’s not supported”.
Thank you in advance.

Hi @Crisps,
Thank you for your question.
@pcanal should be able to help you.

Any update?

Hi,

Just to say upfront, I’m not a ROOT expert, this explanation is based on my own experience working with ROOT and C++ smart pointers, and on the error messages I’ve seen [1]. Hopefully it helps!

I suspect the main technical reason ROOT does not support saving any class containing smart pointers like std::shared_ptr or std::unique_ptr directly into a TTree (or TFile) is that ROOT’s serialization system (the streamer) cannot understand the internal structure of these smart pointers.

When ROOT saves an object, it needs to know how to “stream” (serialize and deserialize) every data member. ROOT uses dictionaries generated from the class definitions that tell it the type of each member and how to save/load it.

However, smart pointers (std::shared_ptr, std::unique_ptr) are complex C++ classes holding internal data such as pointers, reference counters, and allocators. Because ROOT cannot generate streamers for these internal smart pointer types, it issues warnings and errors like these (from my experience), for shared pointers:

Warning in <TStreamerInfo::Build>: shared_ptr<int> has no streamer or dictionary, data member "int_ptr" will not be saved
Error in <TStreamerInfo::Build>: __shared_ptr<int>, discarding: int* _M_ptr, no [dimension]
Warning in <TStreamerInfo::Build>: allocator<int>: base class __gnu_cxx::new_allocator<int> has no streamer or dictionary it will not be saved

Or when trying to save std::unique_ptr:

Error in <TStreamerInfo::Build>: myDetectorData, unknown type: int int_ptr

I hope @pcanal can better clarify this point :slight_smile:

Best,
Alvaro
[1] GitHub - atolosadelgado/customTTreeExample

2 Likes

ROOT does not support saving any class containing smart pointers like std::shared_ptr or std::unique_ptr

ROOT fully support std::unique_ptr.

We have not found a good semantic to support shared_ptr. We could ‘easily’ store the pointee of a shared_ptr but we have no answer to the question of what to do if several shared_ptr to the same pointee are stored accross I/O transaction: for example in separate split branch or separate top level branch or even in separate TKey (in object stored directly in the TFile). Do we store a copy for each of those cases? (leading to duplication of the data on disk). When reading back do we keep the related shared_ptr independent? Implementing the ‘yes’ version to those 2 questions is straight-forward, but is it the right semantic? Is it what the user expected? Most likely it is not, in particular the reading back part (upon reading the shared_ptr would not be sharing anything …). Implementing the no version to either of those question is challenging. The existing version of that is the TRef and TRefArray classes which requires instrumentation of the pointee class (It must derived from TObject) and have found both challenging to use and challenging to maintain.

So until we can resolved the semantic (and technical) issue surrounding std::shared_ptr, we are leaving it unsupported (as well std::weak_ptr etc.)

Or when trying to save std::unique_ptr:

Error in <TStreamerInfo::Build>: myDetectorData, unknown type: int int_ptr

This is a different issue. In this case, I see (at the time I retrieve your repository):

class myDetectorData {
....
    std::shared_ptr<int> int_ptr;
};

so I guess you might have change it to:

class myDetectorData {
....
    std::unique_ptr<int> int_ptr;
};

which is not support ‘as-is’ since we guess that have a pointer to a single int is … inefficient, we assume that the pointer to int is actually a pointer to an array of ints and you would need to provide the size (the number of elements in the array) (there is a syntax for it but it is not recommended). And at that pointer you are better off using a `std::vector. So for that case use either:

class myDetectorData {
....
    int int_value;
};

or

class myDetectorData {
....
    std::vector<int> int_values;
};
3 Likes

Hi @pcanal

thanks for the clarifications!

I am sorry, I forgot to push the change in the code from shared to unique pointer to GitHub.

I tried with the latest version of ROOT, but the leaf with the unique pointer is not saved into the output file and the warning message persist. I will open a new thread to not pollute this one.

Best,
Alvaro

Thank you for the very detailed explanation! I have a question:

what to do if several shared_ptr to the same pointee are stored accross I/O transaction: for example in separate split branch or separate top level branch or even in separate TKey (in object stored directly in the TFile ). Do we store a copy for each of those cases? (leading to duplication of the data on disk).

In this case what we do for the raw pointer? Do we only store a single pointee and leave all pointers pointing to it (even cross-entry or cross-branch)?
And if we do so why the std::shared_ptr is difficult to implement similar feature?
Sorry I’m not an expert for c++ so my pour understanding led me to the question above.

In this case what we do for the raw pointer?

In first approximation you replace them with unique_ptr.

However you answer implies that you still need to have several objects pointing to the same pointee. This is challenging and requires a case by case answer. In the simplest case you would readd the connection after reading the objects. For example:

auto main_owner = file->Get<MainOwnerType>(main_owner_name);
auto secondary = file->Get<Secondary>(secondary_name);
secondary->SetMainOwner(main_owner);
secondary->SetInternalNotOwningPointer(main_owner->GetInternalStuff());

This example is of course over-simplified and not appealing but is there for illustration purpose.

For real code, one would need to really analyze the ‘reasons’ behind the interconnection between object and ideally find a way to design this interconnection without (or with less) use of direct pointers.