c++memory-managementshared-ptrre2stack-allocation

Managing the Lifecycle of re2::RE2 Objects with Cached and Stack-Allocated Options


I have a function re02_match(regexp, use_cache, ...) where I need to handle re2::RE2 objects differently based on whether a cached object should be used (use_cache = true) or a stack-allocated object should be created (use_cache = false). I want to avoid heap allocation for the re2::RE2 objects. Here's a snippet of what I've tried so far:

re2::RE2 *re02;
if (use_cache) {
    //re2_get_cache() will return a shared_ptr<RE2>
    re02 = re2_get_cache(regexp).get();
} else {
    re2::RE2::Options options;
    re2::RE2 re02_in_stack(regexp, options);
    re02 = &re02_in_stack;
}

// Work with *re02
// ...

However, I understand that this approach is flawed because the lifetime of re02_in_stack is limited to the scope in which it is defined, and using .get() on a shared_ptr in this manner is problematic. How can I correctly manage the lifecycle of re2::RE2 objects in a way that avoids heap allocation and ensures that the objects are valid and accessible throughout the execution of re02_match? Any insights or alternative approaches would be greatly appreciated.


Solution

  • I have two options in mind.

    first is using an std::optional and only emplace it in the false path.

    #include <iostream>
    #include <optional>
    
    struct Foo
    {
        void Bar(int c)
        {
            std::cout << "called with: " << c << '\n';
        }
    };
    
    int main()
    {
        int value = 5;
        bool use_cache = true;
    
        std::optional<Foo> foo_opt;
        std::shared_ptr<Foo> foo_ptr;
        Foo* foo = nullptr;
    
        if (use_cache)
        {
            foo_ptr = std::make_shared<Foo>();
            foo = foo_ptr.get();
        }
        else
        {
            foo_opt.emplace(); // constructor args here
            foo = &(*foo_opt);
        }
    
        foo->Bar(value);
    }
    

    the second option is to use a callback that you call in both branches, the disadvantage is that the order of code is reversed, with code appearing first happening last.

    #include <iostream>
    #include <optional>
    
    struct Foo
    {
        void Bar(int c)
        {
            std::cout << "called with: " << c << '\n';
        }
    };
    
    int main()
    {
        int value = 5;
        bool use_cache = true;
    
        auto action = [&](Foo& foo)
            {
                foo.Bar(value);
            };
    
        if (use_cache)
        {
            auto foo_ptr = std::make_shared<Foo>();
            action(*foo_ptr);
        }
        else
        {
            Foo foo;
            action(foo);
        }
    
    }