Search code examples
c++smart-pointersunique-ptr

Achieving constant observance with std::unique_ptr


See first the problem that I'm having, and then my use case (it's possible that I'm misusing std::unique_ptr, so I wanted to include a note on what I'm actually trying to do).

My Problem

I'm trying to find a way to support observance into a std::unique_ptr. That is, have a non-owning function that will take in the contents of the std::unique_ptr, but won't be able to modify any of its components. The intuitive pattern for this would be to write a function taking a const T* object, and then calling that function with MyUniquePtr.get(). But this allows for the following problematic snippet:

#include <memory>

struct unique_wrapper
{
    unique_wrapper(std::unique_ptr<int>&& m) : m(std::move(m))
    {}
    std::unique_ptr<int> m;
};

void foo(const unique_wrapper* w)
{

    // PROBLEM CODE
    *(w->m) = 5;
}

int main()
{
    auto unique_squared = std::make_unique<unique_wrapper>(std::make_unique<int>(0));
    foo(unique_squared.get());
}

The above snippet really scares me, because we completely violate the guarantee of a std::unique_ptr about having a single owner.

And so this leads to my question: what is the natural way to share data in C++ while offering a guarantee that one party won't modify it?

My Use Case

I have a data structure that should have a single owner, but we want to pass this single object to several observing parties that will not take ownership of it (as the owner still has work to do with it), but certainly need to access its contents

auto object = acquire_resources();

// don't want to transfer object ownership!
validate_object_state(object);

// use object further
...

Solution

  • Your problem is const-correctness. A class that exposes a pointer (including a smart pointer) is not const-correct. Make it const-correct by making m private and exposing operator* and operator-> instead:

    struct unique_wrapper {
        unique_wrapper(std::unique_ptr<int>&& m) : m(std::move(m)) {}
    
        // add non-const versions only if unique_wrapper is meant to modify data as non-const
        int& operator*() noexcept { return *m; }
        const int& operator*() const noexcept { return *m; }
    
        // operator-> is not very useful for pointer of int, but I guess your real problem is more complex
        int* operator->() noexcept { return m.get(); }
        const int* operator->() const noexcept { return m.get(); }
    
    private:
        std::unique_ptr<int> m;
    };
    
    // prefer a reference when you are sure a pointer will never be null
    void foo(const unique_wrapper* w)
    {
        **w = 5; // error, it's const
    }
    
    // prefer a reference when you are sure a pointer will never be null
    void bar(unique_wrapper* w)
    {
        **w = 5; // fine, can modify
    }
    

    See it online