You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
7.0 KiB

// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_WEAK_PTR_H_
#define UTIL_WEAK_PTR_H_
#include <memory>
#include <utility>
#include "util/osp_logging.h"
namespace openscreen {
// Weak pointers are pointers to an object that do not affect its lifetime,
// and which may be invalidated (i.e. reset to nullptr) by the object, or its
// owner, at any time; most commonly when the object is about to be deleted.
//
// Weak pointers are useful when an object needs to be accessed safely by one
// or more objects other than its owner, and those callers can cope with the
// object vanishing and e.g. tasks posted to it being silently dropped.
// Reference-counting such an object would complicate the ownership graph and
// make it harder to reason about the object's lifetime.
//
// EXAMPLE:
//
// class Controller {
// public:
// void SpawnWorker() { new Worker(weak_factory_.GetWeakPtr()); }
// void WorkComplete(const Result& result) { ... }
// private:
// // Member variables should appear before the WeakPtrFactory, to ensure
// // that any WeakPtrs to Controller are invalidated before its members
// // variable's destructors are executed, rendering them invalid.
// WeakPtrFactory<Controller> weak_factory_{this};
// };
//
// class Worker {
// public:
// explicit Worker(WeakPtr<Controller> controller)
// : controller_(std::move(controller)) {}
// private:
// void DidCompleteAsynchronousProcessing(const Result& result) {
// if (controller_)
// controller_->WorkComplete(result);
// delete this;
// }
// const WeakPtr<Controller> controller_;
// };
//
// With this implementation a caller may use SpawnWorker() to dispatch multiple
// Workers and subsequently delete the Controller, without waiting for all
// Workers to have completed.
//
// ------------------------- IMPORTANT: Thread-safety -------------------------
//
// Generally, Open Screen code is meant to be single-threaded. For the few
// exceptional cases, the following is relevant:
//
// WeakPtrs may be created from WeakPtrFactory, and also duplicated/moved on any
// thread/sequence. However, they may only be dereferenced on the same
// thread/sequence that will ultimately execute the WeakPtrFactory destructor or
// call InvalidateWeakPtrs(). Otherwise, use-during-free or use-after-free is
// possible.
//
// openscreen::WeakPtr and WeakPtrFactory are similar, but not identical, to
// Chromium's base::WeakPtrFactory. Open Screen WeakPtrs may be safely created
// from WeakPtrFactory on any thread/sequence, since they are backed by the
// thread-safe bookkeeping of std::shared_ptr<>.
template <typename T>
class WeakPtrFactory;
template <typename T>
class WeakPtr {
public:
WeakPtr() = default;
~WeakPtr() = default;
// Copy/Move constructors and assignment operators.
WeakPtr(const WeakPtr& other) : impl_(other.impl_) {}
WeakPtr(WeakPtr&& other) noexcept : impl_(std::move(other.impl_)) {}
WeakPtr& operator=(const WeakPtr& other) {
impl_ = other.impl_;
return *this;
}
WeakPtr& operator=(WeakPtr&& other) noexcept {
impl_ = std::move(other.impl_);
return *this;
}
// Create/Assign from nullptr.
WeakPtr(std::nullptr_t) {} // NOLINT
WeakPtr& operator=(std::nullptr_t) {
impl_.reset();
return *this;
}
// Copy/Move constructors and assignment operators with upcast conversion.
template <typename U>
WeakPtr(const WeakPtr<U>& other) : impl_(other.as_std_weak_ptr()) {}
template <typename U>
WeakPtr(WeakPtr<U>&& other) noexcept
: impl_(std::move(other).as_std_weak_ptr()) {}
template <typename U>
WeakPtr& operator=(const WeakPtr<U>& other) {
impl_ = other.as_std_weak_ptr();
return *this;
}
template <typename U>
WeakPtr& operator=(WeakPtr<U>&& other) noexcept {
impl_ = std::move(other).as_std_weak_ptr();
return *this;
}
// Accessors.
T* get() const { return impl_.lock().get(); }
T& operator*() const {
T* const pointer = get();
OSP_DCHECK(pointer);
return *pointer;
}
T* operator->() const {
T* const pointer = get();
OSP_DCHECK(pointer);
return pointer;
}
// Allow conditionals to test validity, e.g. if (weak_ptr) {...}
explicit operator bool() const { return get() != nullptr; }
// Conversion to std::weak_ptr<T>. It is unsafe to convert in the other
// direction. See comments for private constructors, below.
const std::weak_ptr<T>& as_std_weak_ptr() const& { return impl_; }
std::weak_ptr<T> as_std_weak_ptr() && { return std::move(impl_); }
private:
friend class WeakPtrFactory<T>;
// Called by WeakPtrFactory<T> and the WeakPtr<T> upcast conversion
// constructors and assigners. These are purposely not being exposed publicly
// because that would allow a WeakPtr<T> to be valid/invalid by a different
// ownership/threading model than the intended one (see top-level comments).
template <typename U>
explicit WeakPtr(const std::weak_ptr<U>& other) : impl_(other) {}
template <typename U>
explicit WeakPtr(std::weak_ptr<U>&& other) noexcept
: impl_(std::move(other)) {}
std::weak_ptr<T> impl_;
};
// Allow callers to compare WeakPtrs against nullptr to test validity.
template <typename T>
bool operator!=(const WeakPtr<T>& weak_ptr, std::nullptr_t) {
return weak_ptr.get() != nullptr;
}
template <typename T>
bool operator!=(std::nullptr_t, const WeakPtr<T>& weak_ptr) {
return weak_ptr.get() != nullptr;
}
template <typename T>
bool operator==(const WeakPtr<T>& weak_ptr, std::nullptr_t) {
return weak_ptr.get() == nullptr;
}
template <typename T>
bool operator==(std::nullptr_t, const WeakPtr<T>& weak_ptr) {
return weak_ptr == nullptr;
}
template <typename T>
class WeakPtrFactory {
public:
explicit WeakPtrFactory(T* instance) { Reset(instance); }
WeakPtrFactory(WeakPtrFactory&& other) noexcept = default;
WeakPtrFactory& operator=(WeakPtrFactory&& other) noexcept = default;
// Thread-safe: WeakPtrs may be created on any thread/seuence. They may also
// be copied and moved on any thread/sequence. However, they MUST only be
// dereferenced on the same thread/sequence that calls the destructor or
// InvalidateWeakPtrs().
WeakPtr<T> GetWeakPtr() const {
return WeakPtr<T>(std::weak_ptr<T>(bookkeeper_));
}
// Destruction and Invalidation: These must be called on the same
// thread/sequence that dereferences any WeakPtrs to avoid use-after-free
// bugs.
~WeakPtrFactory() = default;
void InvalidateWeakPtrs() { Reset(bookkeeper_.get()); }
private:
WeakPtrFactory(const WeakPtrFactory& other) = delete;
WeakPtrFactory& operator=(const WeakPtrFactory& other) = delete;
void Reset(T* instance) {
// T is owned externally to WeakPtrFactory. Thus, provide a no-op Deleter.
bookkeeper_ = {instance, [](T* instance) {}};
}
// Manages the std::weak_ptr's referring to T. Does not own T.
std::shared_ptr<T> bookkeeper_;
};
} // namespace openscreen
#endif // UTIL_WEAK_PTR_H_