为什么不能从惟一的_ptr 构造一个弱_ptr?

如果我理解正确的话,weak_ptr不会增加托管对象的引用计数,因此它不代表所有权。它只是允许您访问一个对象,该对象的生存期由其他人管理。 所以我不明白为什么 weak_ptr不能由 unique_ptr构建,而只能由 shared_ptr构建。

有人能简单解释一下吗?

36649 次浏览

std::weak_ptr can't be used unless you convert it to std::shared_ptr by the means of lock(). if the standard allowed what you suggest, that means that you need to convert std::weak_ptr to unique in order to use it, violating the uniqueness (or re-inventing std::shared_ptr)

In order to illustrate, look at the two pieces of code:

std::shared_ptr<int> shared = std::make_shared<int>(10);
std::weak_ptr<int> weak(shared);


{
*(weak.lock()) = 20; //OK, the temporary shared_ptr will be destroyed but the pointee-integer still has shared  to keep it alive
}

Now with your suggestion:

std::unique_ptr<int> unique = std::make_unique<int>(10);
std::weak_ptr<int> weak(unique);


{
*(weak.lock()) = 20; //not OK. the temporary unique_ptr will be destroyed but unique still points at it!
}

That has been said, you may suggest that there is only one unique_ptr, and you still can dereference weak_ptr (without creating another unique_ptr) then there is no problem. But then what is the difference between unique_ptr and shared_ptr with one reference? or moreover, what is the difference between a regular unique_ptr and C-pointers an get by using get?

weak_ptr is not for "general nonowning resources", it has a very specific job - The main goal of weak_ptr is to prevent circular pointing of shared_ptr which will make a memory leak. Anything else needs to be done with plain unique_ptr and shared_ptr.

If you think about it, a weak_ptr must refer to something other than the object itself. That's because the object can cease to exist (when there are no more strong pointers to it) and the weak_ptr still has to refer to something that contains the information that the object no longer exists.

With a shared_ptr, that something is the thing that contains the reference count. But with a unique_ptr, there is no reference count, so there is no thing that contains the reference count, thus nothing to continue to exist when the object is gone. So there's nothing for a weak_ptr to refer to.

There would also be no sane way to use such a weak_ptr. To use it, you'd have to have some way to guarantee that the object wasn't destroyed while you were using it. That's easy with a shared_ptr -- that's what a shared_ptr does. But how do you do that with a unique_ptr? You obviously can't have two of them, and something else must already own the object or it would have been destroyed since your pointer is weak.

Conceptually, there is nothing preventing an implementation where a weak_ptr only provides access and a unique_ptr controls the lifetime. However, there are problems with that:

  • unique_ptr doesn't use reference counting to begin with. Adding the management structure for managing the weak references would be possible, but require an additional dynamic allocation. Since unique_ptr is supposed to avoid any(!) runtime overhead over a raw pointer, that overhead is not acceptable.
  • In order to use the object referenced by a weak_ptr, you need to extract a "real" reference from it, which will first validate that the pointer is not expired first and then give you this real reference (a shared_ptr in this case). This means that you suddenly have a second reference to an object that is supposed to be uniquely owned, which is a recipe for errors. This can't be fixed by returning a mixed half-strong pointer that only temporarily delays possible destruction of the pointee, because you could just as well store that one, too, defeating the idea behind unique_ptr.

Just wondering, what problem are you trying to solve using a weak_ptr here?

A shared_ptr basically has two parts:

  1. the pointed-to object
  2. the reference count object

Once the reference count drops to zero the object (#1) is deleted.

The weak_ptr needs to be able to know if the object (#1) still exists. In order to do this, it has to be able to see the reference count object (#2), if it's not zero it can create a shared_ptr for the object (by incrementing the reference count). If the count is zero it will return an empty shared_ptr.

Consider the question of when can the reference count object (#2) be deleted? We must wait until no shared_ptr OR weak_ptr object refer to it. For this purpose the reference count object holds two reference counts, a strong ref and a weak ref. The reference count object will only be deleted when both these counts are zero. This means that part of the memory can only be freed after all the weak references are gone (this implies a hidden disadvantage with make_shared).

tl;dr; weak_ptr depends on a weak reference count which is part of shared_ptr, there cannot be a weak_ptr without a shared_ptr.

No-one has mentioned the performance aspect of the problem yet, so let me throw my $0.02 in.

weak_ptr must somehow know when the corresponding shared_ptrs have all gone out of scope and the pointed object has been deallocated and destroyed. This means that shared_ptrs need to communicate the destruction towards each weak_ptr to the same object somehow. This has a certain cost – for example, a global hash table needs to be updated, where weak_ptr gets the address from (or nullptr if the object is destroyed).

This also involves locking in a multi-threaded environment, so it can potentially be too slow for some tasks.

However, the goal of unique_ptr is to provide a zero-cost RAII-style abstraction class. Hence, it should not incur any other cost than that of deleteing (or delete[]ing) the dynamically allocated object. The delay imposed by doing a locked or otherwise guarded hash table access, for example, may be comparable to the cost of deallocation, however, which is not desirable in the case of unique_ptr.

It may be useful to distinguish reasons for preferring a unique_ptr over a shared_ptr.

Performance One obvious reason is computational-time and memory use. As currently defined, shared_ptr objects typically need something like a reference-count value, which takes space and also must be actively maintained. unique_ptr objects don't.

Semantics With a unique_ptr, you as the programmer have a pretty good idea when the pointed-to object is going to be destroyed: when the unique_ptr is destroyed or when one of its modifying methods is called. And so on large or unfamiliar code bases, using unique_ptr statically conveys (and enforces) some information about the program's runtime behavior that might not be obvious.

The comments above have generally focused on the performance-based reasons that it would be undesirable for weak_ptr objects to be tied to unique_ptr objects. But one might wonder if the semantics-based argument is sufficient reason for some future version of the STL to support the use-case implied by the original question.

Looks like everyone is writing here about std::weak_ptr but not about weak poiner concept which I believe is what the author is asking for

I think that no one mentioned why standard library is not providing weak_ptr for unique_ptr. Weak pointer CONCEPT doesn't disclaims unique_ptr usage. Weak pointer is only an information if the object has been already deleted so it's not a magic but very simple generalized observer pattern.

It's because of threadsafety and consistency with shared_ptr.

You just simply can't guarantee that your weak_ptr (created from unique_ptr existing on other thread) will be not destroyed during calling method on pointed object. It's because weak_ptr needs to be consistent with std::shared_ptr which guarantee threadsafety. You can implement weak_ptr which works correctly with unique_ptr but only on the same thread - lock method will be unnecessary in this case. You can check chromium sources for base::WeakPtr and base::WeakPtrFactory - you can use it freely with unique_ptr. Chromium weak pointer code is most probably based on last member destruction - you need to add factory as a last member and after that I believe that WeakPtr is informed abut object deleteion (I'm not 100% sure) - so it doesn't looks so hard to implement.

Overall using unique_ptr with weak pointer concept is OK IMHO.

After many years of c++ programing works, i finally realized that the correct way in c++ world is not to use shared_ptr/weak_ptr for ever. We have spent so many time to fix the bugs caused by the unclear ownership of share_ptr.

The solution is to use unque_ptr and some weak pointer for unique_ptr instead, just as what's expected in this topic.

cpp standard library has no weak pointer for unique_ptr, so i create a very simple, but useful libary here --

https://github.com/xhawk18/noshared_ptr

It has two new smart pointers,

noshared_ptr<T>, a new kind of unique_ptr
noweak_ptr<T>, the weak pointer for noshare_ptr<T>

I demonstrated the problem to myself with a MWE implementing weak_ptr on single objects. (I implement it on X here, but X can be anything that can tell us when it dies, such as a unique_ptr with a custom deleter).

The ultimate problem however is that at some point we need reference counting on the weak pointers themselves, because while X is not shared, the weak pointers are shared. This takes us full circle back to using shared_ptr again.

Perhaps the only advantage of doing this is that the intent of single ownership is clearer and cannot be violated, however, as Stroustrup suggests and is quoted this answer, this can be hinted at with the using statement.

#include <iostream>
#include <memory>


template<typename T>
struct ControlBlock
{
T* Target;
explicit ControlBlock(T* target) : Target(target) {}
};


template<typename T>
struct WeakReference
{
std::shared_ptr<ControlBlock<T>> ControlBlock;
T* Get() { return ControlBlock ? ControlBlock->Target : nullptr; }
};


template<typename T>
struct WeakReferenceRoot
{
WeakReference<T> _weakRef;
WeakReferenceRoot(T* target) : _weakRef{std::make_shared<ControlBlock<T>>(target)} { }
const WeakReference<T>& GetReference() { return _weakRef; }
~WeakReferenceRoot() { _weakRef.ControlBlock->Target = nullptr; }
};


struct Customer
{
WeakReferenceRoot<Customer> Weak{this};
};


int main() {
WeakReference<Customer> someRef;
std::cout << "BEFORE SCOPE - WEAK REFERENCE IS " << someRef.Get() << "\n";
{
Customer obj{};
someRef = obj.Weak.GetReference();
std::cout << "IN SCOPE - WEAK REFERENCE IS " << someRef.Get() << "\n";
}
std::cout << "OUT OF SCOPE - WEAK REFERENCE IS " << someRef.Get() << "\n";
return 0;
}

Sadly as with many cases - cause the C++ committee just didn't care and dismissed such usecases.

How it is: weak_ptr was specified in terms of shared-pointer, precluding any attempts at making it a more broadly useful smart-pointer. In C++ conceptually a weak-ptr is a non-owning pointer that MUST be converted to a shared_ptr to access the underlying object. And as a unique_ptr does not support any sort of reference-counting (as it is the unique owner by definition) converting a weak_ptr to a pointer with any sort of ownership is not allowed. It is sadly a bit too late to get good well-named smart-pointers that offer a bit more generality.

But you can create something like that:

To make it safe you would need your own deleter (unique_ptr has the deleter in the type), and a new non-owning unique_ptr_observer that changes the deleter. When creating an observer it would register a cleanup-handler as the deleter. That way you can have a unique_ptr_observer that can check if the Threadsafety would still be a problem as you would need either a locking-mechanism, create copies for readout, or some other way to prevent the pointer from getting deleted while you are actively looking at it. (it is so annoying that the deleter is part of the type.......)