I'm considering using "suicide objects" to model entities in a game, that is, objects able to delete themselves. Now, the usual C++03 implementation (plain old delete this) does nothing for other objects potentially refering to the suicide object, which is why I'm using std::shared_ptr and std::weak_ptr.
Now for the code dump :
#include <memory>
#include <iostream>
#include <cassert>
struct SuObj {
SuObj() { std::cout << __func__ << '\n'; }
~SuObj() { std::cout << __func__ << '\n'; }
void die() {
ptr.reset();
}
static std::weak_ptr<SuObj> create() {
std::shared_ptr<SuObj> obj = std::make_shared<SuObj>();
return (obj->ptr = std::move(obj));
}
private:
std::shared_ptr<SuObj> ptr;
};
int main() {
std::weak_ptr<SuObj> obj = SuObj::create();
assert(!obj.expired());
std::cout << "Still alive\n";
obj.lock()->die();
assert(obj.expired());
std::cout << "Deleted\n";
return 0;
}
Question
This code appears to work fine. However, I'd like to have someone else's eye to gauge it. Does this code make sense ? Did I blindly sail into undefined lands ? Should I drop my keyboard and begin art studies right now ?
I hope this question is sufficiently narrowed down for SO. Seemed a bit tiny and low-level for CR.
Minor precision
I do not intend to use this in multithreaded code. If the need ever arises, I'll be sure to reconsider the whole thing.
When you have shared_ptr based object lifetime, the lifetime of your object is the "lifetime" of the union of the shared_ptrs who own it collectively.
In your case, you have an internal shared_ptr, and your object will not die until that internal shared_ptr expires.
However, this does not mean you can commit suicide. If you remove that last reference, your object continues to exist if anyone has .lock()'d the weak_ptr and stored the result. As this is the only way you can access the object externally, it may happen1.
In short, die() can fail to kill the object. It might better be called remove_life_support(), as something else could keep the object alive after said life support is removed.
Other than that, your design works.
1
You could say "well, then callers should just not keep the shared_ptr around" -- but that doesn't work, as the check that the object is valid is only valid as long as the shared_ptr persists. Plus, by exposing the way to create shared_ptr, you have no type guarantees that the client code won't store them (accidentally or on purpose).
A transaction based model (where you pass a lambda in, and it operates on it internally) could help with this if you want seriously paranoid robustness.
Or you can live with the object sometimes living too long.
Consider hiding these messy details behind a Regular Type (or almost-regular) that has a pImpl to the nasty memory management problem. That pImpl could be a weak_ptr with the above semantics.
Then users of your code need only interact with the Regular (or pseudoRegular) wrapper.
If you don't want cloning to be easy, disable copy construction/assignment and only expose move.
Now your nasty memory management is hiding behind a fascade, and if you decide you did it all wrong the external pseudoRegular interface can have different guts.
Regular type in C++11
Not a direct answer but potentially useful information.
In Chromium codebase there is a concept of exactly what you are trying to achieve. They call it WeakPtrFactory. Their solution cannot be directly taken into your code since they have their own implementation of e.g. shared_ptr and weak_ptr but design wise it can be of use to you.
I made a try to implement it and found out that the problem of double deletion can be solved by passing into inner shared_ptr custom empty deleter - from this moment on neither shared_ptrs created from weak_ptr not inner shared_ptr will be able to call destructor (again) on your object.
The only problem to solve is what if your object is deleted and somewhere else you keep shared_ptr to it? But from what I see it cannot be simply solved by any magic mean and require designing that whole project the way that it simply never happens e.g. by using shared_ptr only in local scope and ensuring that some set of operations (creating suicide object, using it, ordering its suicide) could be performed only in the same thread.
I understand you're trying to create a minimal example for SO, but I see a few challenges you'll want to consider:
You have a public constructor and destructor, so technically there's no guarantee that the create() method is always used.
You could make those protected or private but that decision would interfere with use with std algorithms and containers.
This doesn't guarantee that the object will actually destruct because as long as someone has a shared_ptr it's going to exist. That may or may not be a problem for your use case, but because of that I don't think this will add as much value as you're heading.
This is likely going to be confusing and counter-intuitive to other developers. It might make maintenance harder, even if your intent is to make it easier. That's a bit of a value judgement, but I'd encourage you to consider if it's truly easier to manage.
I commend you for putting thought into memory management up front. Disciplined use of shared_ptr and weak_ptr will help with your memory management issues -- I'd counsel against trying to have the instance try to manage its own lifecycle.
As for art studies... I'd only recommend that if that's truly your passion! Good luck!
Related
I have a container of objects stored with unique_ptr, for simplicity say i have only one object :
class Container { std::unique_ptr<A> ptrA; }
I also have class that uses the object. These class take a raw pointer to these objects when they are constructed:
class B { A* a;
B(*A param) : a(param) }
They are created with : B b = B(Container.ptrA.get() );
The Container class is supposed to outlive the class B. However I'd like my whole program not to crash in the case there is an issue or a bug in my class Container and the unique_ptr goes out of scope and get deleted.
My question is about the design you would take to manage this 1% case so my program can try to reload the data and avoid crashing suddenly, would you use exceptions ? If so where would you do try/catch ?
Thanks !
When you use std::unique_ptr you're making a design decision: Container owns the pointer. Trying to work around that fact is only going to make your life harder.
But in fact you said Container outlives B. Why don't you just enforce that instead of being overly defensive against bugs that would probably break your program in several other ways?
I would say don't use shared_ptr to hide bugs. If your unique_ptr is designed to outlive the raw pointer then I would want the program to crash if there is a bug. Then I have something to fix. It's much worse when the bugs go undetected because they are hidden from you. Remember, a crash gives you a point of failure to investigate. But if the bugs go undetected you may not be able to find what's making things go wrong.
If you'd like your program not to crash, then use std::shared_ptr for both pointers.
That would be the easiest solution.
Otherwise, you will need to put in some kind of a mechanism by which the Container class tracks the number of instances of the B class, that use the same pointer, then throw an exception in the destructor if the Container is getting destroyed while there are still an instance of B somewhere. If its unique_ptr is getting blown away for some other reason, other than the destructor getting invoked, the same check would apply there, as well.
That's presuming that throwing an exception is what you would like to do to handle this edge case. It's not clear what you mean "can try to reload the data", but as then designer and the implementer of your application you need to decide how you are going to handle this situation. Nobody else can make the call for you, you know more about your overall application than anyone else. There is no universal, single answer here that will work best for every application in every situation.
But whatever you decide should be an appropriate course of action: throw an exception; or create a new instance of the object, stuff it into the unique_ptr and then update all native pointers in all the B classes that you're keeping track of, somehow; that would be your call to make. What's the best approach is a subjective call to make. There is no objective answer for that part.
Now, getting back to the technical aspects, keeping track of how many instances of the B class can be as simple as keeping a counter in the container, and have B's constructor and destructor update it accordingly. Or maybe have Container keep a container of pointers to all instances of B. In either case, don't forget to do the right thing in the copy constructor and the assignment operator.
But I think it's just easier to use use a std::shared_ptr in both classes, and not worry about any of this. Even though doing this kind of class bookkeeping is not rocket science, why bother when you can simply have std::shared_ptr do this for you.
Philosophically: this is not a great idea, at least in C++.
The Container class is supposed to outlive the class B. However I'd like my whole program not to crash in the case there is an issue or a bug ...
It sounds like you want a "safer" language.
The idea that you can write code that "should" work but is robust against ownership/lifetime errors is...pretty much anathema to the goals of low-level languages like C++ with explicit lifetime management, I think.
If you really want to write a program that simply doesn't crash, use a language with a runtime that manages memory and lifetimes for you—that is, a garbage-collected language like Java or Python. These languages are designed to "protect you from yourself," so to speak. In theory, they prevent you from encountering the sorts of errors you're describing by managing memory for you.
But part of the point of using low-level languages is to take advantage of explicit memory management. With C++ you can (in theory) write software that, in comparison to software written in managed languages, runs faster, has a smaller memory footprint, and releases system resources (such as filehandles) sooner.
The right approach in C++ is the one you're already using.
Explicitly letting your container class own the underlying objects and representing this ownership using unique_ptr is exactly correct in modern C++, and there is no reason to expect this approach not to work if your system is carefully engineered.
The key question, though, is how can you guarantee that your container class will stay alive and keep your owned objects alive throughout the entire lifetime of the "user" objects (in this case class B instances)? Your question doesn't provide enough details about your architecture for us to answer this, because different designs will require different approaches. But if you can explain how your system (in theory) provides this guarantee, then you are probably on the right track.
If you still have concerns, there are some approaches for dealing with them.
There are many reasons to have valid concerns about lifetime management in C++; a major one is if you are inheriting a legacy codebase and you're not sure it manages lifetimes appropriately.
This can happen even with modern C++ features such as unique_ptr. I'm working on a project that only got started last year, and we've been using C++14 features, including <memory>, since the beginning, and I definitely consider it a "legacy" project:
Multiple engineers who were on the project have now left; 60,000+ lines are "unowned" in the sense that their original author is no longer on the project
There are very few unit tests
There are occasional segfaults :D
Note that a bug in your lifetime management may not cause a crash; if it did, that would be fantastic, because, as Galik says in their answer, this would give you a point of failure to investigate. Unfortunately, there's no way to guarantee that dereferencing a stale pointer will cause a crash, because this is (obviously) undefined behavior. Thus your program could keep running and silently do something utterly disastrous.
Signal-catching
However, a crash—specifically, a segfault—is the most likely result of the error you describe, because a segfault is something you can (sort of) program around.
This is the weakest approach in terms of what kinds of fault-handling behavior you can implement: simply catch the SEGFAULT signal and try to recover from it. Signal-catching functions have some pretty severe limitations, and in general if your lifetime management is screwed up there's probably no way to make reasonable guarantees about what memory you can trust and what memory you can't, so your program might be doomed no matter what you do when you catch the signal.
This is not a good approach to "fixing" broken software; however, it is a very reasonable way to provide a clean exit path for unrecoverable errors (e.g. it will allow you to emulate the classic "memory error at " error messages). Additionally, if all you want to do is to restart your entire application and hope for the best, you can probably implement this using a signal-catcher, although a better approach may be to implement a second "watcher" application that restarts your software when it crashes.
std::shared_ptr
Joachim Pileborg is correct that a std::shared_ptr will work in this case, but (1) shared_ptr has some overhead compared to raw pointers (if you care about that) and (2) it requires changing your entire lifetime-management scheme.
Also, as pointed out by Galik in the comments, when there is a lifetime-management bug, the lifetime of the owned object will be extended; the object will still exist after the shared_ptr has been removed from the container if any shared_ptrs in your B class instances are still active.
std::weak_ptr
Your best bet might be a weak_ptr. This also requires changing your lifetime-management scheme to use shared_ptr, but it has the benefit of not keeping old objects around just because a shared_ptr to them exists somewhere outside of your lifetime-managing containers.
Not all low-level languages are this unforgiving, though.
I'm a bit biased because I love the philosophies behind the Rust language, so this is a bit of a plug. Rust enforces correct lifetime-management at compile-time. Rust is just as low-level as C++ in the sense that it gives full control over memory management, memory access, etc., but it's a "modern" high-level language in that it's closer to a redesigned version of C++ than it is to, say, C.
But the key point for our purposes is that the limitations Rust puts on you in terms of what it considers an "ownership" or lifetime-management error enable far better guarantees of program correctness than any possible static analysis of a C or C++ program ever could.
The system programming language Rust uses the ownership paradigm to ensure at compile time with zero cost for the runtime when a resource has to be freed.
In C++ we commonly use smart pointers to achieve the same goal of hiding the complexity of managing resource allocation. There are a couple of differences though:
In Rust there is always only one owner, whereas C++ shared_ptr can easily leak ownership.
In Rust we can borrow references we do not own, whereas C++ unique_ptr cannot be shared in a safe way via weak_ptr and lock().
Reference counting of shared_ptr is costly.
My question is: How can we emulate the ownership paradigm in C++ within the following constraints:
Only one owner at any time
Possibility to borrow a pointer and use it temporarily without fear of the resource going out of scope (observer_ptr is useless for this)
As much compile-time checks as possible.
Edit: Given the comments so far, we can conclude:
No compile-time support for this (I was hoping for some decltype/template magic unknown to me) in the compilers. Might be possible using static analysis elsewhere (taint?)
No way to get this without reference counting.
No standard implementation to distinguish shared_ptrs with owning or borrowing semantic
Could roll your own by creating wrapper types around shared_ptr and weak_ptr:
owned_ptr: non-copyable, move-semantics, encapsulates shared_ptr, access to borrowed_ptr
borrowed_ptr: copyable, encapsulates weak_ptr, lock method
locked_ptr: non-copyable, move-semantics, encapsulates shared_ptr from locking weak_ptr
You can't do this with compile-time checks at all. The C++ type system is lacking any way to reason about when an object goes out of scope, is moved, or is destroyed — much less turn this into a type constraint.
What you could do is have a variant of unique_ptr that keeps a counter of how many "borrows" are active at run time. Instead of get() returning a raw pointer, it would return a smart pointer that increments this counter on construction and decrements it on destruction. If the unique_ptr is destroyed while the count is non-zero, at least you know someone somewhere did something wrong.
However, this is not a fool-proof solution. Regardless of how hard you try to prevent it, there will always be ways to get a raw pointer to the underlying object, and then it's game over, since that raw pointer can easily outlive the smart pointer and the unique_ptr. It will even sometimes be necessary to get a raw pointer, to interact with an API that requires raw pointers.
Moreover, ownership is not about pointers. Box/unique_ptr allows you to heap allocate an object, but it changes nothing about ownership, life time, etc. compared to putting the same object on the stack (or inside another object, or anywhere else really). To get the same mileage out of such a system in C++, you'd have to make such "borrow counting" wrappers for all objects everywhere, not just for unique_ptrs. And that is pretty impractical.
So let's revisit the compile time option. The C++ compiler can't help us, but maybe lints can? Theoretically, if you implement the whole life time part of the type system and add annotations to all APIs you use (in addition to your own code), that may work.
But it requires annotations for all functions used in the whole program. Including private helper function of third party libraries. And those for which no source code is available. And for those whose implementation that are too complicated for the linter to understand (from Rust experience, sometimes the reason something is safe are too subtle to express in the static model of lifetimes and it has to be written slightly differently to help the compiler). For the last two, the linter can't verify that the annotation is indeed correct, so you're back to trusting the programmer. Additionally, some APIs (or rather, the conditions for when they are safe) can't really be expressed very well in the lifetime system as Rust uses it.
In other words, a complete and practically useful linter for this this would be substantial original research with the associated risk of failure.
Maybe there is a middle ground that gets 80% of the benefits with 20% of the cost, but since you want a hard guarantee (and honestly, I'd like that too), tough luck. Existing "good practices" in C++ already go a long way to minimizing the risks, by essentially thinking (and documenting) the way a Rust programmer does, just without compiler aid. I'm not sure if there is much improvement over that to be had considering the state of C++ and its ecosystem.
tl;dr Just use Rust ;-)
What follows are some examples of ways people have tried to emulate parts of Rust's ownership paradigm in C++, with limited success:
Lifetime safety: Preventing common dangling. The most thorough and rigorous approach, involving several additions to the language to support the necessary annotations. If the effort is still alive (last commit was in 2019), getting this analysis added to a mainstream compiler is probably the most likely route to "borrow checked" C++. Discussed on IRLO.
Borrowing Trouble: The Difficulties Of A C++ Borrow-Checker
Is it possible to achieve Rust's ownership model with a generic C++ wrapper?
C++Now 2017: Jonathan Müller “Emulating Rust's borrow checker in C++" (video) and associated code, about which the author says, "You're not actually supposed to use that, if you need such a feature, you should use Rust."
Emulating the Rust borrow checker with C++ move-only types and part II (which is actually more like emulating RefCell than the borrow checker, per se)
I believe you can get some of the benefits of Rust by enforcing some strict coding conventions (which is after all what you'd have to do anyway, since there's no way with "template magic" to tell the compiler not to compile code that doesn't use said "magic"). Off the top of my head, the following could get you...well...kind of close, but only for single-threaded applications:
Never use new directly; instead, use make_unique. This goes partway toward ensuring that heap-allocated objects are "owned" in a Rust-like manner.
"Borrowing" should always be represented via reference parameters to function calls. Functions that take a reference should never create any sort of pointer to the refered-to object. (It may in some cases be necessary to use a raw pointer as a paramter instead of a reference, but the same rule should apply.)
Note that this works for objects on the stack or on the heap; the function shouldn't care.
Transfer of ownership is, of course, represented via R-value references (&&) and/or R-value references to unique_ptrs.
Unfortunately, I can't think of any way to enforce Rust's rule that mutable references can only exist anywhere in the system when there are no other extant references.
Also, for any kind of parallelism, you would need to start dealing with lifetimes, and the only way I can think of to permit cross-thread lifetime management (or cross-process lifetime management using shared memory) would be to implement your own "ptr-with-lifetime" wrapper. This could be implemented using shared_ptr, because here, reference-counting would actually be important; it's still a bit of unnecessary overhead, though, because reference-count blocks actually have two reference counters (one for all the shared_ptrs pointing to the object, another for all the weak_ptrs). It's also a little... odd, because in a shared_ptr scenario, everybody with a shared_ptr has "equal" ownership, whereas in a "borrowing with lifetime" scenario, only one thread/process should actually "own" the memory.
I think one could add a degree of compile-time introspection and custom sanitisation by introducing custom wrapper classes that track ownership and borrowing.
The code below is a hypothetical sketch, and not a production solution which would need a lot more tooling, e.g. #def out the checks when not sanitising. It uses a very naive lifetime checker to 'count' borrow errors in ints, in this instance during compilation. static_asserts are not possible as the ints are not constexpr, but the values are there and can be interrogated before runtime. I believe this answers your 3 constraints, regardless of whether these are heap allocations, so I'm using a simple int type to demo the idea, rather than a smart pointer.
Try uncommenting the use cases in main() below (run in compiler explorer with -O3 to see boilerplate optimise away), and you'll see the warning counters change.
https://godbolt.org/z/Pj4WMr
// Hypothetical Rust-like owner / borrow wrappers in C++
// This wraps types with data which is compiled away in release
// It is not possible to static_assert, so this uses static ints to count errors.
#include <utility>
// Statics to track errors. Ideally these would be static_asserts
// but they depen on Owner::has_been_moved which changes during compilation.
static int owner_already_moved = 0;
static int owner_use_after_move = 0;
static int owner_already_borrowed = 0;
// This method exists to ensure static errors are reported in compiler explorer
int get_fault_count() {
return owner_already_moved + owner_use_after_move + owner_already_borrowed;
}
// Storage for ownership of a type T.
// Equivalent to mut usage in Rust
// Disallows move by value, instead ownership must be explicitly moved.
template <typename T>
struct Owner {
Owner(T v) : value(v) {}
Owner(Owner<T>& ov) = delete;
Owner(Owner<T>&& ov) {
if (ov.has_been_moved) {
owner_already_moved++;
}
value = std::move(ov.value);
ov.has_been_moved = true;
}
T& operator*() {
if (has_been_moved) {
owner_use_after_move++;
}
return value;
}
T value;
bool has_been_moved{false};
};
// Safely borrow a value of type T
// Implicit constuction from Owner of same type to check borrow is safe
template <typename T>
struct Borrower {
Borrower(Owner<T>& v) : value(v.value) {
if (v.has_been_moved) {
owner_already_borrowed++;
}
}
const T& operator*() const {
return value;
}
T value;
};
// Example of function borrowing a value, can only read const ref
static void use(Borrower<int> v) {
(void)*v;
}
// Example of function taking ownership of value, can mutate via owner ref
static void use_mut(Owner<int> v) {
*v = 5;
}
int main() {
// Rather than just 'int', Owner<int> tracks the lifetime of the value
Owner<int> x{3};
// Borrowing value before mutating causes no problems
use(x);
// Mutating value passes ownership, has_been_moved set on original x
use_mut(std::move(x));
// Uncomment for owner_already_borrowed = 1
//use(x);
// Uncomment for owner_already_moved = 1
//use_mut(std::move(x));
// Uncomment for another owner_already_borrowed++
//Borrower<int> y = x;
// Uncomment for owner_use_after_move = 1;
//return *x;
}
The use of static counters is obviously not desirable, but it is not possible to use static_assert as owner_already_moved is non-const. The idea is these statics give hints to errors appearing, and in final production code they could be #defed out.
You can use an enhanced version of a unique_ptr (to enforce a unique owner) together with an enhanced version of observer_ptr (to get a nice runtime exception for dangling pointers, i.e. if the original object maintained through unique_ptr went out of scope). The Trilinos package implements this enhanced observer_ptr, they call it Ptr. I have implemented the enhanced version of unique_ptr here (I call it UniquePtr): https://github.com/certik/trilinos/pull/1
Finally, if you want the object to be stack allocated, but still be able to pass safe references around, you need to use the Viewable class, see my initial implementation here: https://github.com/certik/trilinos/pull/2
This should allow you to use C++ just like Rust for pointers, except that in Rust you get a compile time error, while in C++ you get a runtime exception. Also, it should be noted, that you only get a runtime exception in Debug mode. In Release mode, the classes do not do these checks, so they are as fast as in Rust (essentially as fast as raw pointers), but then they can segfault. So one has to make sure the whole test suite runs in Debug mode.
Suppose we have a class that looks like the following.
class DoStuffWithRef
{
DoStuffWithRef(LargeObject& lo) : lo_(lo) {}
// a bunch of member functions, some of them useful
// [...]
private:
LargeObject& lo_;
};
The class is designed such that the only sane use is when at least one other entity has ownership of the object that lo_ references. If client code uses the class appropriately, there's no need for DoStuffWithRef to have ownership of the LargeObject.
Is there a way to enforce this usage or signal an error if the class is being misused? Can anything be done besides documenting the intended use of DoStuffWithRef?
For example, an automatically stored DoStuffWithRef might refer to an automatically stored LargeObject.
void foo()
{
LargeObject lo;
DoStuffWithRef dswr(lo);
// some code that makes use of the DoStuffWithRef instance
return;
}
This is the primary usage I have in mind although there are other possible cases where someone else might be guaranteed to have ownership.
The problem is that it's very possible for client code to create a DoStuffWithRef instance that will end up with a dangling reference. If no one else has ownership of a LargeObject referred to by a DoStuffWithRef, then chaos ensues when the the DoStuffWithRef tries to access the LargeObject. Worse yet, chaos may ensue only long after the error has been made.
When this has come up in the past, I've used a boost::shared_ptr<> instead of a reference (this was in C++03). That doesn't quite express the semantics of the class appropriately. The premise is that DoStuffWithRef doesn't need ownership -- it is meant to act on objects owned by others and if no one else owns the object then the functionality provided by DoStuffWithRef makes no sense. Giving ownership to DoStuffWithRef has unnecessary overhead and forces shared ownership semantics. weak_ptr<> would also have the wrong semantics because we shouldn't be asking at runtime if the pointer is valid.
I've never had this scenario come up in a performance sensitive section of code or in a case where shared ownership was a significant burden so it just hasn't really mattered but I wish I knew of a way to accurately express this in C++. The costs are small but they're also unnecessary. More importantly, a shared_ptr<> will hide incorrect use of the class. If a DoStuffWithRef, modified to use a shared_ptr<>, is the only remaining owner of a LargeObject, then the dangling reference has been averted in the short term but the client code ends up in uncharted territory.
Is DoStuffWithRef otherwise stateful? If not, it seems like it is a bucket of utility functions. The reference to LargeObject could just as well be passed as an argument to each function. That also resolves the question of ownership.
Otherwise, if it is stateful, this is a clear case of shared ownership, since DoStuffWithRef really does need to extend the lifetime of a single live instance of LargeObject to that of itself. Or, use a weak pointer and eat the cost of the runtime check...
I'd like to know if there is any smart pointer type concept that implements the "very weak reference" idea.
This would be basically a weak_ptr but that cannot be turned into a shared_ptr, basically, when you have very_weak_refs out-there, you are sure that the strong-ref count can never go up.
This would allow better "strong ownership" of memory by managers, and delivering very weak references in the wild would still allow clients to access the data by using good old raw pointer through a .lock_get() function or equivalent... (name designed to mirror what you would have doing .lock().get() usually).
You don't have the same security on data, because your object may be destroyed while you use it, but if your environment is controlled enough so that you know that the manager's cannot clean its data while you are processing, you're still good to use raw-pointers locally after having checked against null_ptr after lock_get().
Do any of you wished for similar thing, more info/intelligence/thoughts on that ?
thanks.
rationale : the motivation behind is that weak_ptr has the "security flaw" of being turnable to shared and therefore, after distributing weak references in the wild, you basically did the same than distributing shared ones because anybody can keep very long lived shared refs on your data effectively preventing correct cleaning by the entities that were suppsed to be strong (the manager).
This is solved by very-weak-refs, when you distribute that kind of objects in your manager's public interface, you are sure that when you delete your last shared ref, your data is deleted.
For me, the whole concept of weak references works only with well behaved clients; who understands that they should promotes their weak refs into shareds for only small amounts of time.
Unfortunately, what you are asking for is impossible with the traditional interface of smart pointers.
The issue is one of lifetime. A weak_ptr cannot be use to access the object directly, because it does not guarantee that said object will live long enough: the object might be pulled right from under your feet.
Example:
int main() {
std::shared_ptr<int> sp(new int(4));
std::weak_ptr<int> wp(sp);
if (not wp.expired()) {
sp.reset();
std::cout << *wp << "\n"; // Access WP ? But there is nothing there!
}
}
Thus, for better or worse, there is no other choice than recovering a shared pointer from the weak pointer any time you actually need to access the object without controlling the duration of this access.
This last point, however, is our clue. A simple idea being to write a well-behaved client of weak_ptr yourself and change the way it allows the external world to access the data. For example:
template <typename T>
class very_weak_ptr {
public:
very_weak_ptr() {}
explicit very_weak_ptr(std::weak_ptr<T> wp): _wp(wp) {}
template <typename F>
void apply(F&& f) {
std::shared_ptr<T> sp = _wp.lock();
f(sp.get());
}
private:
std::weak_ptr<T> _wp;
}; // class very_weak_ptr
Note: there is one remaining flaw, enable_shared_from_this allows to recover a std::shared_ptr<T> from the very instance of T; you can add a compile time check on T to prevent usage of this class with such objects.
What you ask for is functionally equivalent to, starting with a std::shared_ptr<>:
initially creating a std::weakptr<> thereto + .get()-ting a raw pointer,
before use of the raw pointer, checking the weak_ptr<> hasn't .expired().
Do any of you wished for similar thing, more info/intelligence/thoughts on that ? thanks.
No... if you need to check the weak_ptr<>.expired() anyway, you might as well get a valid shared_ptr<>. What do you really think this is going to achieve? 'Better "strong ownership"' when you know somehow / require that the manager can't release the object during the period you're using the raw pointer - doesn't add up....
Generally I follow the Google style guide, which I feel aligns nicely with the way I see things. I also, almost exclusively, use boost::scoped_ptr so that only a single manager has ownership of a particular object. I then pass around naked pointers, the idea being that my projects are structured such that the managers of said objects are always destroyed after the objects that use them are destroyed.
http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Smart_Pointers
This is all great, however I was just bitten by a nasty little memory stomp bug where the owner just so happened to be deleted before objects that were using it were deleted.
Now, before everyone jumps up and down that I'm a fool for this pattern, why don't I just use shared_ptr ? etc., consider the point that I don't want to have undefined owner semantics. Although shared_ptr would have caught this particular case, it sends the wrong message to users of the system. It says, "I don't know who owns this, it could be you!"
What would have helped me would have been a weak pointer to a scoped pointer. In effect, a scoped pointer that has a list of weak references, that are nulled out when the scoped pointer destructs. This would allow single ownership semantics, but give the using objects a chance to catch the issue I ran into.
So at the expense of an extra 'weak_refs' pointer for the scoped_ptr and an extra pointer for the 'next_weak_ptr' in the weak_ptr, it would make a neat little single owner, multiple user structure.
It could maybe even just be a debug feature, so in 'release' the whole system just turns back into a normally sized scoped_ptr and a standard single pointer for the weak reference.
So..... my QUESTIONS after all of this are:
Is there such a pointer/patten already in stl/boost that I'm
missing, or should I just roll my own?
Is there a better way, that
still meets my single ownership goal?
Cheers,
Shane
2. Is there a better way, that still meets my single ownership goal?
Do use a shared_ptr, but as a class member so that it's part of the invariant of that class and the public interface only exposes a way to obtain a weak_ptr.
Of course, pathological code can then retain their own shared_ptr from that weak_ptr for as long as they want. I don't recommend trying to protect against Machiavelli here, only against Murphy (using Sutter's words). On the other hand, if your use case is asynchronous, then the fact that locking a weak_ptr returns a shared_ptr may be a feature!
Although shared_ptr would have caught this particular case, it sends the wrong message to users of the system. It says, "I don't know who owns this, it could be you!"
A shared_ptr doesn't mean "I don't know who owns this". It means "We own this." Just because one entity does not have exclusive ownership does not mean that anyone can own it.
The purpose of a shared_ptr is to ensure that the pointer cannot be destroyed until everyone who shares it is in agreement that it ought to be destroyed.
Is there such a pointer/patten already in stl/boost that I'm missing, or should I just roll my own?
You could use a shared_ptr exactly the same way you use a scoped_ptr. Just because it can be shared doesn't mean you have to share it. That would be the easiest way to work; just make single-ownership a convention rather than an API-established rule.
However, if you need a pointer that is single-owner and yet has weak pointers, there isn't one in Boost.
I'm not sure that weak pointers would help that much. Typically, if a
component X uses another component Y, X must be informed of Y's demise,
not just to nullify the pointer, but perhaps to remove it from a list,
or to change its mode of operation so that it no longer needs the
object. Many years ago, when I first started C++, there was a flurry of
activity trying to find a good generic solution. (The problem was
called relationship management back then.) As far as I know, no good
generic solution was every found; at least, every project I've worked on
has used a hand built solution based on the Observer pattern.
I did have a ManagedPtr on my site, when it was still up, which behaved
about like what you describe. In practice, except for the particular
case which led to it, I never found a real use for it, because
notification was always needed. It's not hard to implement, however;
the managed object derives from a ManagedObject class, and gets all of
the pointers (ManagedPtr, and not raw pointers) it hands out from it.
The pointer itself is registered with the ManagedObject class, and the
destructor of the ManagedObject class visits them all, and "disconnects"
them by setting the actual pointer to null. And of course, ManagedPtr
has an isValid function so that the client code can test before
dereferencing. This works well (regardless of how the object is
managed—most of my entity objects "own" themselves, and do a
delete this is response to some specific input), except that you tend
to leak invalid ManagedPtr (e.g. whenever the client keeps the pointer
in a container of some sort, because it may have more than one), and
clients still aren't notified if they need to take some action when your
object dies.
If you happen to be using QT, QPointer is basically a weak pointer to a QObject. It connects itself to the "I just got destroyed" event in the pointed-to value, and invalidates itself automatically as needed. That's a seriously hefty library to pull in for what amounts to bug tracking, though, if you aren't already jumping through QT's hoops.