std::vector has the member function at() as a safe alternative to operator[], so that bound checking is applied and no dangling references are created:
void foo(std::vector<int> const&x)
{
const auto&a=x[0]; // What if x.empty()? Undefined behavior!
const auto&a=x.at(0); // Throws exception if x.empty().
}
However, std::unique_ptr lacks the corresponding functionality:
void foo(std::unique_ptr<int> const&x)
{
const auto&a=*x; // What if bool(x)==false? Undefined behavior!
}
It would be great, if std::unique_ptr had such a safe alternative, say member ref() (and cref()) which never returns a dangling reference, but rather throws an exception. Possible implementation:
template<typename T>
typename add_lvalue_reference<T>::type
unique_ptr<T>::ref() const noexcept(false)
{
if(bool(*this)==false)
throw run_time_error("trying to de-refrence null unique_ptr");
return this->operator*();
}
Is there any good reason why the standard doesn't provide this sort of thing?
unique_ptr was specifically designed as a lightweight pointer class with null-state detection (e.g. stated in optional in A proposal to add a utility class to represent optional objects (Revision 3))
That said, the capability you're asking is already in-place since operator* documentation states:
// may throw, e.g. if pointer defines a throwing operator*
typename std::add_lvalue_reference<T>::type operator*() const;
The pointer type is defined as
std::remove_reference<Deleter>::type::pointer if that type exists, otherwise T*
Therefore through your custom deleter you're able to perform any on-the-fly operation including null pointer checking and exception throwing
#include <iostream>
#include <memory>
struct Foo { // object to manage
Foo() { std::cout << "Foo ctor\n"; }
Foo(const Foo&) { std::cout << "Foo copy ctor\n"; }
Foo(Foo&&) { std::cout << "Foo move ctor\n"; }
~Foo() { std::cout << "~Foo dtor\n"; }
};
struct Exception {};
struct InternalPtr {
Foo *ptr = nullptr;
InternalPtr(Foo *p) : ptr(p) {}
InternalPtr() = default;
Foo& operator*() const {
std::cout << "Checking for a null pointer.." << std::endl;
if(ptr == nullptr)
throw Exception();
return *ptr;
}
bool operator != (Foo *p) {
if(p != ptr)
return false;
else
return true;
}
void cleanup() {
if(ptr != nullptr)
delete ptr;
}
};
struct D { // deleter
using pointer = InternalPtr;
D() {};
D(const D&) { std::cout << "D copy ctor\n"; }
D(D&) { std::cout << "D non-const copy ctor\n";}
D(D&&) { std::cout << "D move ctor \n"; }
void operator()(InternalPtr& p) const {
std::cout << "D is deleting a Foo\n";
p.cleanup();
};
};
int main()
{
std::unique_ptr<Foo, D> up(nullptr, D()); // deleter is moved
try {
auto& e = *up;
} catch(Exception&) {
std::cout << "null pointer exception detected" << std::endl;
}
}
Live Example
For completeness' sake I'll post two additional alternatives/workarounds:
Pointer checking for a unique_ptr via operator bool
#include <iostream>
#include <memory>
int main()
{
std::unique_ptr<int> ptr(new int(42));
if (ptr) std::cout << "before reset, ptr is: " << *ptr << '\n';
ptr.reset();
if (ptr) std::cout << "after reset, ptr is: " << *ptr << '\n';
}
(This would probably be the clanest way to deal with the issue)
An alternative solution, although messier, is to use a wrapper type which takes care of the exception handling
I suspect the real answer is simple, and the same one for lots of "Why isn't C++ like this?" questions:
No-one proposed it.
std::vector and std::unique_ptr are not designed by the same people, at the same time, and are not used in the same way, so don't necessarily follow the same design principles.
I can't say, why the committee decided not to add a safe dereferenciation method - the answer is probably "because it wasn't proposed" or "because a raw pointer hasn't one either". But it is trivial to write a free function template on your own that takes any pointer as an argument, compares it against nullptr and then either throws an excepion or returns a reference to the pointed to object.
If you don't delete it via a pointer to base class, it should be even possible to derive publicly from a unique_ptr and just add such a member function.
Keep in mind however that using such a checked method everywhere might incur a significant performance hit (same as at). Usualy you want to validate your parameters at most once, for which a single if statement at the beginning is much better suited.
There is also the school that says you should not throw exceptions in response to programming errors. Maybe the peopke in charge of designing unique_ptr belonged to this school, while the people designing vector(which is much much older) didn't.
One of the main goals of a smart pointer API design is to be a drop-in replacement with added value, no gotchas or side effects, and close to zero overhead. if (ptr) ptr->... is how safe access to bare pointer is usually done, the same syntax works nicely with smart pointers thus requiring no code change when one is replaced with the other.
An additional check for validity (say, to throw an exception) put inside a pointer would interfere with branch predictor and thus may have a knock-on effect on the performance, which may not be considered a zero cost drop-in replacement anymore.
You do have
operator bool()
Example from:
cplusplusreference
// example of unique_ptr::operator bool
#include <iostream>
#include <memory>
int main () {
std::unique_ptr<int> foo;
std::unique_ptr<int> bar (new int(12));
if (foo) std::cout << "foo points to " << *foo << '\n';
else std::cout << "foo is empty\n";
if (bar) std::cout << "bar points to " << *bar << '\n';
else std::cout << "bar is empty\n";
return 0;
}
unique_ptr is a simple wrapper to a raw pointer, no need to throw an exception when you can just check a boolean condition easily.
Edit:
Apparently operator* can throw.
Exceptions
1) may throw, e.g. if pointer defines a throwing operator*
Maybe someone could shed some lights on hot to define a throwing operator*
Following from the suggestion of MikeMB, here is a possible implementation of a free function for dereferencing pointers and unique_ptrs alike.
template<typename T>
inline T& dereference(T* ptr) noexcept(false)
{
if(!ptr) throw std::runtime_error("attempt to dereference a nullptr");
return *ptr;
}
template<typename T>
inline T& dereference(std::unique_ptr<T> const& ptr) noexcept(false)
{
if(!ptr) throw std::runtime_error("attempt to dereference an empty unique_ptr)");
return *ptr;
}
Related
I have to implement a simple "unique_ptr" class supporting only a constructor, destructor, –>, *, and release(). And I did below.
However, it feels weird to write "up.operator->()" to get the pointer p. I would be more logical to write "up->p". But how do I do that? Thanks!
#include <iostream>
#include <stdexcept>
template <class T>
class unique_ptr
{
T *p;
public:
unique_ptr(T *ptr)
: p{ptr}
{
}
~unique_ptr() { delete p; }
T *operator->() const { return p; } // returns a pointer
T operator*() const { return *p; }
T *release()
{
T *ptr = p;
p = nullptr;
return ptr;
}
};
template <class T>
void print(const unique_ptr<T> &up, const std::string &s)
{
std::cout << s << " up.operator->(): " << up.operator->() << '\n';
std::cout << s << " up.operator*(): " << up.operator*() << '\n';
}
int main()
try
{
int *ptr = new int(10);
unique_ptr<int> up(ptr);
print(up, "up: ");
}
catch (std::exception &e)
{
std::cerr << "exception: " << e.what() << '\n';
return 1;
}
catch (...)
{
std::cerr << "exception\n";
return 2;
}
However, it feels weird to write "up.operator->()" to get the pointer p.
It feels weird because the member access operator is not generally used to get a pointer to the object (although you can do it using the operator->() syntax, as you demonstrated). Member access operator is used to access members of the object. In your example, you have a unique pointer of int. int doesn't have a member, so it doesn't make sense to use the member access operator.
Here is an example of how to use it:
struct S {
int member;
};
unique_ptr<S> up(new S{10});
int value_of_member = up->member;
would be more logical to write "up->p"
That wouldn't be logical unless p is a member of the pointed object.
How to create an operator-> for a class unique_ptr
Like you did in the example. As far as I can tell, there was no problem with how you create the operator, but rather how to use it.
P.S. Your unique pointer is copyable, movable and assignable, but those operations are horribly broken leading to undefined behaviour. See rule of 5.
As others have noted in comments this implementation of a single ownership smart pointer is incomplete and the operator*() is incorrect in that it doesn't return a reference and thus does not facilitate making assignments through the pointer.
However to answer the question,
it feels weird to write "up.operator->()" to get the pointer p. I
would be more logical to write "up->p". But how do I do that?
Well you wouldnt want to do that as p is part of the private implementation of your smart pointer class. It is weird to write up.operator->() because that is not how the -> is typically used. It is typically used as shorthand to access the members of a struct or class that is slightly less verbose than the * operator in combination with member access via .. To use your pointer then in a less weird way you need the template parameter to be instantiated with some type that has fields, e.g.
struct foo {
int bar;
};
void print(const unique_ptr<foo>& up, const std::string& s)
{
std::cout << s << " up.operator->(): " << up->bar << '\n';
std::cout << s << " up.operator*(): " << (*up).bar << '\n';
}
int main()
{
unique_ptr<foo> up(new foo{ 42 });
print(up, "up: ");
}
Currently, I'm storing a collection of std::unique_ptrs to heap allocated objects of a polymorphic type:
struct Foo {
virtual ~Foo() = default;
};
Collection<std::unique_ptr<Foo>> foos;
The basic interface I need is putting / taking owners of Foo to / from foos. The objects stored in foos are never supposed to be nullptr so I'd like to replace runtime assert(owner_taken) with compile-time checks. Moreover, I would like to be capable of using non-null owners in the context where a nullable one may be expected.
Probably, I need to store something like unique_ref but how would I extract one from foos? I don't want a copy, I want the stored object itself, so owner->clone() isn't a solution. Neither I can std::move(owner) because the state of a "unique reference" would be invalid afterwards.
Is there a clean design decision?
Is there a never-null unique owner of heap allocated objects?
There is no such type in the standard library.
It is possible to implement such type. Simply define a type with unique_ptr member and mark the default constructor deleted. You can mark constructor from std::nullptr_t deleted also so that construction from nullptr will be prevented at compile time.
Whether you construct from an external pointer, or allocate in the constructor, there is no alternative for checking for null at runtime.
Reading your question, I interpret the following requirements:
You don't want to copy or move the object itself (Foo)
You don't want a wrapper which has some sort of empty state which excludes move semantics
The object itself (Foo) should be stored on the heap such that its lifetime is independent of the control flow
The object itself (Foo) should be deleted once it is not used any more
As construction / destruction, copy and move are the only ways to get objects into / out of a container, the only thing left is a wrapper object which is copied when moved into / out of the container.
You can create such an object yourself as follows:
// LICENSE: MIT
#include <memory>
#include <utility>
template<typename T>
class shared_ref {
public:
template<typename ...Args>
shared_ref(Args&&... args)
: data{new T(std::forward<Args>(args)...)}
{}
shared_ref(shared_ref&&) = delete;
shared_ref& operator=(shared_ref&&) = delete;
T& get() noexcept {
return *data;
}
const T& get() const noexcept {
return *data;
}
operator T&() noexcept {
return get();
}
operator const T&() const noexcept {
return get();
}
void swap(shared_ref& other) noexcept {
return data.swap(other);
}
private:
std::shared_ptr<T> data;
};
template<class T>
void swap(shared_ref<T>& lhs, shared_ref<T>& rhs) noexcept {
return lhs.swap(rhs);
}
I leave it as an exercise to the reader to add support for Allocator, Deleter, operator[], implicit conversion contructor to base classes.
This can then be used as follows:
#include <iostream>
int main() {
shared_ref<int> r; // default initialized
std::cout << "r=" << r << std::endl;
r = 5; // type conversion operator to reference
std::cout << "r=" << r << std::endl;
shared_ref<int> s{7}; // initialized with forwarded arguments
std::cout << "s=" << s << std::endl;
std::swap(r, s);
std::cout << "r=" << r << ", " << "s=" << s << std::endl;
s = r; // copy assignment operator
std::cout << "s=" << s << std::endl;
const shared_ref<int> t{s}; // copy constructor
std::cout << "t=" << t << std::endl;
//t = 8; // const ref from a const object is immutable
return 0;
}
I am writing an implementation of the Haskell Maybe Monad in C++11.
However I got stuck when I tried to test the code. When I construt a value of the type with the pseudo constructor Just and then try to evaluate it with using the function fromJust (that should just "unpack" the value placed inside the Maybe) the program stops and eventually terminates silently.
So i tried to debug it; here is the output for the code of testMaybe.cpp:
c1
yeih2
value not null: 0xf16e70
I added a couple of print statements to evaluate where the program stops, and it seems to stop at the exact point where I dereference the pointer to return the value. (I have marked it in the code.)
At first I thought that the value in the maybe might have been deconstructed by the time i want to dereference the pointer, which, to my understanding, would result in undefined behaviour or termination. However, I was unable to find the place where that would have happened.
Can you please give me a hint on why this is happening?
testMaybe.cpp:
#include<iostream>
#include "Maybe.hpp"
using namespace std;
using namespace Functional_Maybe;
int main() {
Maybe<string> a{Maybe<string>::Just("hello") };
if(!isNothing(a)) cout << "yeih2 " << fromJust(a) << endl;
return 0;
}
Maybe.hpp
#pragma once
#include<stdexcept>
#include<iostream>
using namespace std;
namespace Functional_Maybe {
template <typename T>
class Maybe {
const T* value;
public:
Maybe(T *v) : value { v } {} //public for return in join
const static Maybe<T> nothing;
static Maybe<T> Just (const T &v) { cout << "c1" << endl; return Maybe<T> { new T(v) }; }
T fromJust() const {
if (isNothing()) throw std::runtime_error("Tried to extract value from Nothing");
cout << "\nvalue not null: " << value << " " << *value << endl;
// ^ stops here
return *value;
}
bool isNothing() const { return value==nullptr; }
~Maybe() { if (value != nullptr) delete value; }
};
template <typename T>
bool isNothing(Maybe<T> val) {
return val.isNothing();
}
template <typename T>
T fromJust(Maybe<T> val) {
return val.fromJust();
}
}
You class template Maybe owns resources (the dynamically allocated T), but does not follow the Rule of Three: the (implicitly defined) copy and move operations do shallow copies only, which leads to use-after-free and double-free problems. You should either implement proper copy and move operations (cosntructors and assignment operators) for your class, or use std::unique_ptr<const T> as the type of value, and remove your manual destructor (thereby following the preferred Rule of Zero).
Side note: have you looked into std::optional (or, in pre-C++17 versions, boost::optional)? They seem to be doing something very similar (or even identical) to your proposed class, and you might want to use them instead (or use them internally in your class if that suits you better). They might even be more efficient, using small object optimisation to avoid dynamic memory allocation in some cases.
I wanted to restrict a specific class to be creatable on the stack only (not via allocation). The reason for this is that on the stack, the object which lifetime has begun last, will be the first to be destroyed, and I can create a hierarchy. I did it like this:
#include <cstddef>
#include <iostream>
class Foo {
public:
static Foo createOnStack() {
return {};
}
~Foo () {
std::cout << "Destructed " << --i << std::endl;
}
protected:
static int i;
Foo () {
std::cout << "Created " << i++ << std::endl;
}
Foo (const Foo &) = delete;
};
int Foo::i = 0;
The constructor normally should push the hierarchy stack, and the destructor pops it. I replaced it here for proof of concept. Now, the only way you can use such an object is by storing it in a temporary reference like this:
int main() {
Foo && a = Foo::createOnStack();
const Foo& b = Foo::createOnStack();
return 0;
}
My question now is, how safe is this with the C++ standard? Is there still a way to legally create a Foo on the heap or hand it down from your function into another frame (aka return it from your function) without running into undefined behaviour?
EDIT: link to example https://ideone.com/M0I1NI
Leaving aside the protected backdoor, C++17 copy elision breaks this in two ways:
#include<iostream>
#include<memory>
struct S {
static S make() {return {};}
S(const S&)=delete;
~S() {std::cout << '-' << this << std::endl;}
private:
S() {std::cout << '+' << this << std::endl;}
};
S reorder() {
S &&local=S::make();
return S::make();
}
int main() {
auto p=new S(S::make()),q=new S(S::make()); // #1
delete p; delete q;
reorder(); // #2
}
The use of new is obvious and has been discussed.
C++17 also allows prvalues to propagate through stack frames, which means that a local can get created before a return value and get destroyed while that return value is alive.
Note that the second case already existed (formally in C++14 and informally long before) in the case where local is of type S but the return value is some other (movable) type. You can't assume in general that even automatic object lifetimes nest properly.
Consider this program:
#include <memory>
#include <iostream>
class X
: public std::enable_shared_from_this<X>
{
public:
struct Cleanup1 { void operator()(X*) const; };
struct Cleanup2 { void operator()(X*) const; };
std::shared_ptr<X> lock1();
std::shared_ptr<X> lock2();
};
std::shared_ptr<X> X::lock1()
{
std::cout << "Resource 1 locked" << std::endl;
return std::shared_ptr<X>(this, Cleanup1());
}
std::shared_ptr<X> X::lock2()
{
std::cout << "Resource 2 locked" << std::endl;
return std::shared_ptr<X>(this, Cleanup2());
}
void X::Cleanup1::operator()(X*) const
{
std::cout << "Resource 1 unlocked" << std::endl;
}
void X::Cleanup2::operator()(X*) const
{
std::cout << "Resource 2 unlocked" << std::endl;
}
int main()
{
std::cout << std::boolalpha;
X x;
std::shared_ptr<X> p1 = x.lock1();
{
std::shared_ptr<X> p2 = x.lock2();
}
}
I don't see anything in the C++11 Standard section 20.7.2 suggesting any of this is invalid. It's a bit unusual to have two shared_ptr objects store the same pointer &x but not share ownership, and to use "deleters" that do not end the lifetime of *get(), but nothing forbids it. (And if either of those are entirely unintended, it would be difficult to explain why some shared_ptr member functions accept a std::nullptr_t value.) And as expected, the program outputs:
Resource 1 locked
Resource 2 locked
Resource 2 unlocked
Resource 1 unlocked
But now if I add a bit to main():
int main()
{
std::cout << std::boolalpha;
X x;
std::shared_ptr<X> p1 = x.lock1();
bool test1( x.shared_from_this() );
std::cout << "x.shared_from_this() not empty: " << test1 << std::endl;
{
std::shared_ptr<X> p2 = x.lock2();
}
try {
bool test2( x.shared_from_this() );
std::cout << "x.shared_from_this() not empty: " << test2 << std::endl;
} catch (std::exception& e) {
std::cout << "caught: " << e.what() << std::endl;
}
}
then things get trickier. With g++ 4.6.3, I get the output:
Resource 1 locked
x.shared_from_this() not empty: true
Resource 2 locked
Resource 2 unlocked
caught: std::bad_weak_ptr
Resource 1 unlocked
Why would the second call to shared_from_this() fail? All the requirements of 20.7.2.4p7 are met:
Requires: enable_shared_from_this<T> shall be an accessible base class of T. *this shall be a subobject of an object t of type T. There shall be at least one shared_ptr instance p that owns &t.
[T is X, t is x, p is p1.]
But g++'s enable_shared_from_this essentially follows the suggested implementation from the (non-normative) "Note" in 20.7.2.4p10, using a private weak_ptr member in class enable_shared_from_this. And it seems impossible to account for this sort of issue without doing something considerably more complicated in enable_shared_from_this.
Is this a defect in the Standard? (If so, no comment is needed here on what the solution "should" be: add a requirement so the example program invokes Undefined Behavior, change the Note to not suggest such a simple implementation would be sufficient,....)
Yes, there is a defect here in C++11. In allowing this:
It's a bit unusual to have two shared_ptr objects store the same pointer &x but not share ownership, and to use "deleters" that do not end the lifetime of *get(), but nothing forbids it.
This should be explicitly stated to be undefined behavior, regardless of what the "deleters" do. Sure, it may be technically not illegal to do things that way.
However, you are lying to people who use the code. The expectation of anyone who receives a shared_ptr is that they now have ownership of the object. So long as they keep that shared_ptr (or a copy thereof) around, the object it points to will still exists.
That is not the case with your code. So I would say that it is syntactically correct but semantically invalid.
The language for shared_from_this is fine. It's the language for shared_ptr that needs changing. It should state that it is undefined behavior to create two separate unique pointers that "own" the same pointer.
I agree this is a hole in the specification, thus a defect. It's basically the same as http://open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2179 although that issue comes at it from a slightly different (and IMHO more obviously broken) angle.
I'm not sure I agree that this is a misuse of shared_ptr, I think it's fine to do that with shared_ptrs, because unlike the code in issue 2179 you use no-op deleters. I think the problem is when you try to combine that kind of use of shared_ptr with enable_shared_from_this.
So my first thought was to fix it by extending the requirements of shared_from_this:
Requires: enable_shared_from_this<T> shall be an accessible base class of T. *this shall be a subobject of an object t of type T. There shall be at least one shared_ptr instance p that owns &t and any other shared_ptr instances that own &t shall share ownership with p.
This isn't quite sufficient though, because your example meets that requirement: at the second call to shared_from_this() there is only one owner (p1) but you've already "corrupted" the state of the enable_shared_from_this base class by calling lock2().
A smaller form of the program is:
#include <memory>
using namespace std;
int main()
{
struct X : public enable_shared_from_this<X> { };
auto xraw = new X;
shared_ptr<X> xp1(xraw); // #1
{
shared_ptr<X> xp2(xraw, [](void*) { }); // #2
}
xraw->shared_from_this(); // #3
}
All three of libstdc++, libc++ and VC++ (Dinkumware) behave the same and throw bad_weak_ptr at #3, because at #2 they update the weak_ptr<X> member of the base class to make it share ownership with xp2, which goes out of scope leaving the weak_ptr<X> in the expired state.
Interestingly boost::shared_ptr doesn't throw, instead #2 is a no-op and #3 returns a shared_ptr that shares ownership with xp1. This was done in response to a bug report with almost exactly the same example as the one above.
This issue and other related ones were clarified in C++17. Now std::enable_shared_from_this<T> is specified as if having a single std::weak_ptr<T> weak_this; member. For a non-array specialization of std::shared_ptr, that member is assigned by std::shared_ptr constructors, std::make_shared, and std::allocate_shared as described in [util.smartptr.shared.const]/1:
Enables shared_from_this with p, for a pointer p of type Y*, means that if Y has an unambiguous and accessible base class that is a specialization of enable_shared_from_this, then remove_cv_t<Y>* shall be implicitly convertible to T* and the constructor evaluates the statement:
if (p != nullptr && p->weak_this.expired())
p->weak_this = shared_ptr<remove_cv_t<Y>>(*this, const_cast<remove_cv_t<Y>*>(p));
So the correct behavior of the second main in my OP is now that no exception will be thrown and both "not empty" checks will show true. Since at the call to lock2() the internal weak_ptr is already owned and therefore not expired(), lock2() leaves the weak_ptr unchanged, and so the second call to shared_from_this() returns a shared_ptr which shared ownership with p1.
X x;
std::shared_ptr<X> p1 = x.lock1();
(...sniped...)
}
Such code is breaks the semantics of "owning" "smart pointers":
they can be copied
as long as one copy is kept around, the owned object is kept around
This invariant is so essential, I'd argue that such practice should be rejected by code review. But there is a variant of what you suggest that satisfies the invariant:
the object must be dynamically managed (so, not automatic)
any family of owning objects has shared ownership of the dynamically managed object
each member of a family has shared ownership of the "deleter" of that family
So here we have shared owning objects that are part of different "families" of owning objects, they aren't "equivalent" as they have different:
"deleters" object
use_count() values
control blocks
owner_before results
but they all prevent the destruction of the same object; this is done by keeping a copy of the shared_ptr in each and every "deleter" object.
A clean replacement for std::shared_from_this is used to have complete control over the initialization of the std::weak_ptr<T> member.
#include <memory>
#include <iostream>
#include <cassert>
// essentially like std::shared_from_this
// unlike std::shared_from_this the initialization IS NOT implicit
// calling set_owner forces YOU to THINK about what you are doing!
template <typename T>
class my_shared_from_this
{
std::weak_ptr<T> weak;
public:
void set_owner(std::shared_ptr<T>);
std::shared_ptr<T> shared_from_this() const;
};
// shall be called exactly once
template <typename T>
void my_shared_from_this<T>::set_owner(std::shared_ptr<T> shared)
{
assert (weak.expired());
weak = shared;
}
template <typename T>
std::shared_ptr<T> my_shared_from_this<T>::shared_from_this() const
{
assert (!weak.expired());
return weak.lock();
}
class X : public my_shared_from_this<X>
{
public:
struct Cleanup1 {
std::shared_ptr<X> own;
Cleanup1 (std::shared_ptr<X> own) : own(own) {}
void operator()(X*) const;
};
struct Cleanup2 {
std::shared_ptr<X> own;
Cleanup2 (std::shared_ptr<X> own) : own(own) {}
void operator()(X*) const;
};
std::shared_ptr<X> lock1();
std::shared_ptr<X> lock2();
X();
~X();
};
// new shared owner family with shared ownership with the other ones
std::shared_ptr<X> X::lock1()
{
std::cout << "Resource 1 locked" << std::endl;
// do NOT call set_owner here!!!
return std::shared_ptr<X>(this, Cleanup1(shared_from_this()));
}
std::shared_ptr<X> X::lock2()
{
std::cout << "Resource 2 locked" << std::endl;
return std::shared_ptr<X>(this, Cleanup2(shared_from_this()));
}
void X::Cleanup1::operator()(X*) const
{
std::cout << "Resource 1 unlocked" << std::endl;
}
void X::Cleanup2::operator()(X*) const
{
std::cout << "Resource 2 unlocked" << std::endl;
}
X::X()
{
std::cout << "X()" << std::endl;
}
X::~X()
{
std::cout << "~X()" << std::endl;
}
// exposes construction and destruction of global vars
struct GlobDest {
int id;
explicit GlobDest(int id);
~GlobDest();
};
GlobDest::GlobDest(int id)
: id(id)
{
std::cout << "construction of glob_dest #" << id << std::endl;
}
GlobDest::~GlobDest() {
std::cout << "destruction of glob_dest #" << id << std::endl;
}
GlobDest glob_dest0 {0};
std::shared_ptr<X> glob;
GlobDest glob_dest1 {1};
std::shared_ptr<X> make_shared_X()
{
std::cout << "make_shared_X" << std::endl;
std::shared_ptr<X> p = std::make_shared<X>();
p->set_owner(p);
return p;
}
int test()
{
std::cout << std::boolalpha;
std::shared_ptr<X> p = make_shared_X();
static std::shared_ptr<X> stat;
{
std::shared_ptr<X> p1 = p->lock1();
stat = p1;
{
std::shared_ptr<X> p2 = p->lock2();
glob = p2;
std::cout << "exit scope of p2" << std::endl;
}
std::cout << "exit scope of p1" << std::endl;
}
std::cout << "exit scope of p" << std::endl;
}
int main()
{
test();
std::cout << "exit main" << std::endl;
}
Output:
construction of glob_dest #0
construction of glob_dest #1
make_shared_X
X()
Resource 1 locked
Resource 2 locked
exit scope of p2
exit scope of p1
exit scope of p
exit main
Resource 1 unlocked
destruction of glob_dest #1
Resource 2 unlocked
~X()
destruction of glob_dest #0