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).
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?
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
...
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
}