5

I have a ServiceProvider class which contains a couple of pointers to different services, like that:

class ServiceProvider()
{
    Service3* GetService3();

public:
    void Process(Object* o);
    void Shrink();

private:
    TAutoSpawningPtr<Service1> service1;
    TAutoSpawningPtr<Service2> service2;

    Service3* service3;
}

Note that TAutoSpawningPtr is a theoretical smart pointer сlass I'm looking for, and service3 is declared as an ordinary pointer to explicitly show the behaviour I needed. The body of Process():

void ServiceProvider::Process(Object* o)
{
    service1->Process(o);
    service2->Process(o);
    GetService3()->Process(o);
}

The body of GetService3():

void ServiceProvider::GetService3()
{
    if(!service3)
    {
       service3 = new Service3();
    }

    return service3;
}

As you can see, an instance of Service3 is being created lazily and it don't exist until it needed.

Shrink() method is being called periodically to delete all internal services. Like this:

void ServiceProvider::Shrink()
{
    service1.Release(); // delete its internal Service1 pointer if it exists.
    service2.Release(); // delete its internal Service2 pointer if it exists.

    if (service3)
    {
        // delete its internal Service3 pointer if it exists.
        delete service3;
        service3 = nullptr;
    }
}

What do I need: I want TAutoSpawningPtr<> to be a smart pointer class, which automatically creates its class instance by calling the default construcror once I dereference the pointer using an overloaded operator->. An inner resource posessed by the pointer had to be deleted once called the Release() method (and, of course, it had to be recreated when I need it again).

Why do I need this?

  1. To automatically control presence/absence of an object.
  2. To prevent nullptrs when derefenecing pointers directly (like this->service3->Process(o)) instead of indirect GetService3().
  3. To release unused services without explicit checks.

The question is: Does the standard (or any third-party) library have an auto pointer class which will satisfy my needs? And if not, would you kindly to bring me some code examples that shows behavior I need. Thanks.

Netherwire
  • 2,669
  • 3
  • 31
  • 54
  • 1
    Is the service a shared resource or is there only a single owner? If there is only a single owner, the proxy solution from Curious is fine. Otherwise you may need some wrapper around shared or weak pointers. – pschill Jun 28 '17 at 09:09
  • @pschill good observation! updated my answer – Curious Jun 28 '17 at 09:17

1 Answers1

5

The simplest solution here would be to just call a function that initializes the two if they are uninitialized or are not pointing to anything.

But if you really want to, you can create a simple proxy pointer class that does this for you. For example

#include <iostream>
#include <memory>

using std::cout;
using std::endl;

class Something {
public:
    Something() {
        cout << __PRETTY_FUNCTION__ << endl;
    }
    void do_something() {
        cout << __PRETTY_FUNCTION__ << endl;
    }
};

template <typename Type,
          template <typename...> class Ptr = std::unique_ptr>
class AutoAllocatingPtr {
public:
    Type* operator->() {
        if (!this->ptr) {
            this->ptr = Ptr<Type>{new Type{}};
        }
        return this->ptr.get();
    }

    void release() {
        this->ptr.reset();
    }

private:
    Ptr<Type> ptr;
};

int main() {
    // default unique ownership with std::unique_ptr
    auto ptr = AutoAllocatingPtr<Something>{};
    ptr->do_something();
    ptr.release();
    ptr->do_something();

    // if you want shared ownership
    auto s_ptr = AutoAllocatingPtr<Something, std::shared_ptr>{};
    s_ptr->do_something();
    s_ptr.release();
    s_ptr->do_something();
}

Note Note the code in the end and how you can use that to switch the type of ownership semantics that the pointer exhibits.

Curious
  • 20,870
  • 8
  • 61
  • 146
  • The unique_ptr solution has a pitfall: If you move from `AutoAllocatingPtr` with `Ptr=unique_ptr` and reuse the moved-from object, a second instance will be spawned. I think this is against the purpose of a unique_ptr. Do you have an idea how this behavior can be avoided? – pschill Jun 28 '17 at 09:41
  • @pschill i feel like that's what OP would want in the case right? Because essentially that would be releasing the memory that was there in the old instance. – Curious Jun 28 '17 at 09:44
  • @pschill however a simple solution to that case would be to just check if the type of `Ptr` is `unique_ptr` and then throw an exception (`logic_error` or a variant of that) when `operator->` is used on a moved from `AutoAllocatingPtr`. The check would be optimized to be a no-op at runtime in C++14 and will 100% be a no-op at runtime with `if constexpr` in C++17 – Curious Jun 28 '17 at 09:45
  • @pschill although it seems like clang has a bug regarding this! https://wandbox.org/permlink/1rbEA3aQ9FW3rX2E – Curious Jun 28 '17 at 09:50
  • 1
    I am not sure that this is the AutoAllocatingPtr expects as desired by OP. From the name Service / ServiceProvider and since OP mentions smart pointers, I would expect that a service type cannot exist multiple times. But I guess only OP can tell us the exact requirements. – pschill Jun 28 '17 at 09:57
  • @pschill The only difference can be in the `release()` method from what I make from OP's question, but that would be a small change I feel – Curious Jun 28 '17 at 09:59