Improve unique_ref

This commit is contained in:
Sebastian Messmer 2017-04-03 14:43:32 +01:00
parent d84e65ad76
commit 9ada10db4d
7 changed files with 241 additions and 70 deletions

View File

@ -26,12 +26,12 @@ using optional_ownership_ptr = std::unique_ptr<T, std::function<void(T*)>>;
template<typename T>
optional_ownership_ptr<T> WithOwnership(std::unique_ptr<T> obj) {
auto deleter = obj.get_deleter();
return optional_ownership_ptr<T>(obj.release(), [deleter](T* obj){deleter(obj);});
return optional_ownership_ptr<T>(obj.release(), deleter);
}
template <typename T>
optional_ownership_ptr<T> WithOwnership(unique_ref<T> obj) {
return WithOwnership(to_unique_ptr(std::move(obj)));
return WithOwnership(static_cast<std::unique_ptr<T>>(std::move(obj)));
}
template<typename T>

View File

@ -25,141 +25,153 @@ namespace cpputils {
* It will hold a nullptr after its value was moved to another unique_ref.
* Never use the old instance after moving!
*/
template<typename T>
template<class T, class D = std::default_delete<T>>
class unique_ref final {
public:
using element_type = typename std::unique_ptr<T, D>::element_type;
using deleter_type = typename std::unique_ptr<T, D>::deleter_type;
using pointer = typename std::unique_ptr<T, D>::pointer;
unique_ref(unique_ref&& from): _target(std::move(from._target)) {
from._target = nullptr;
}
// TODO Test this upcast-allowing move constructor
template<typename U> unique_ref(unique_ref<U>&& from): _target(std::move(from._target)) {
unique_ref(unique_ref&& from) noexcept: _target(std::move(from._target)) {
from._target = nullptr;
}
unique_ref& operator=(unique_ref&& from) {
template<class U> unique_ref(unique_ref<U>&& from) noexcept: _target(std::move(from._target)) {
from._target = nullptr;
}
unique_ref& operator=(unique_ref&& from) noexcept {
_target = std::move(from._target);
from._target = nullptr;
return *this;
}
// TODO Test this upcast-allowing assignment
template<typename U> unique_ref& operator=(unique_ref<U>&& from) {
template<class U> unique_ref& operator=(unique_ref<U>&& from) noexcept {
_target = std::move(from._target);
from._target = nullptr;
return *this;
}
typename std::add_lvalue_reference<T>::type operator*() const& {
typename std::add_lvalue_reference<element_type>::type operator*() const& noexcept {
ASSERT(_target.get() != nullptr, "Member was moved out to another unique_ref. This instance is invalid.");
return *_target;
}
typename std::add_rvalue_reference<T>::type operator*() && {
typename std::add_rvalue_reference<element_type>::type operator*() && noexcept {
ASSERT(_target.get() != nullptr, "Member was moved out to another unique_ref. This instance is invalid.");
return std::move(*_target);
}
T* operator->() const {
pointer operator->() const noexcept {
return get();
}
T* get() const {
pointer get() const noexcept {
ASSERT(_target.get() != nullptr, "Member was moved out to another unique_ref. This instance is invalid.");
return _target.get();
}
void swap(unique_ref& rhs) {
operator std::unique_ptr<element_type>() && noexcept {
return std::move(_target);
}
operator std::shared_ptr<element_type>() && noexcept {
return std::move(_target);
}
void swap(unique_ref& rhs) noexcept {
std::swap(_target, rhs._target);
}
bool isValid() const {
bool isValid() const noexcept {
return _target.get() != nullptr;
}
private:
unique_ref(std::unique_ptr<T> target): _target(std::move(target)) {}
template<typename U, typename... Args> friend unique_ref<U> make_unique_ref(Args&&... args);
template<typename U> friend boost::optional<unique_ref<U>> nullcheck(std::unique_ptr<U> ptr);
template<typename U> friend class unique_ref;
template<typename DST, typename SRC> friend boost::optional<unique_ref<DST>> dynamic_pointer_move(unique_ref<SRC> &source);
template<typename U> friend std::unique_ptr<U> to_unique_ptr(unique_ref<U> ref);
template<class U> friend U* _extract_ptr(const unique_ref<U> &obj);
deleter_type& get_deleter() noexcept {
return _target.get_deleter();
}
std::unique_ptr<T> _target;
const deleter_type& get_deleter() const noexcept {
return _target.get_deleter();
}
private:
unique_ref(std::unique_ptr<T, D> target) noexcept: _target(std::move(target)) {}
template<class U, class... Args> friend unique_ref<U> make_unique_ref(Args&&... args);
template<class T2, class D2> friend boost::optional<unique_ref<T2, D2>> nullcheck(std::unique_ptr<T2, D2> ptr) noexcept;
template<class T2, class D2> friend class unique_ref;
template<class DST, class SRC> friend boost::optional<unique_ref<DST>> dynamic_pointer_move(unique_ref<SRC> &source) noexcept;
template<class T2, class D2> friend const typename unique_ref<T2, D2>::pointer _extract_ptr(const unique_ref<T2, D2> &obj) noexcept;
std::unique_ptr<T, D> _target;
DISALLOW_COPY_AND_ASSIGN(unique_ref);
};
template<typename T, typename... Args>
template<class T, class... Args>
inline unique_ref<T> make_unique_ref(Args&&... args) {
return unique_ref<T>(std::make_unique<T>(std::forward<Args>(args)...));
}
template<typename T>
inline boost::optional<unique_ref<T>> nullcheck(std::unique_ptr<T> ptr) {
template<class T, class D>
inline boost::optional<unique_ref<T, D>> nullcheck(std::unique_ptr<T, D> ptr) noexcept {
if (ptr.get() != nullptr) {
return unique_ref<T>(std::move(ptr));
return unique_ref<T, D>(std::move(ptr));
}
return boost::none;
}
template<typename T> inline void destruct(unique_ref<T> ptr) {
to_unique_ptr(std::move(ptr)).reset();
template<class T, class D> inline void destruct(unique_ref<T, D> /*ptr*/) {
// ptr will be moved in to this function and destructed on return
}
template<class T> T* _extract_ptr(const unique_ref<T> &obj) {
template<class T, class D> const typename unique_ref<T, D>::pointer _extract_ptr(const unique_ref<T, D> &obj) noexcept {
return obj._target.get();
}
//TODO Also allow passing a rvalue reference, otherwise dynamic_pointer_move(func()) won't work
template<typename DST, typename SRC>
inline boost::optional<unique_ref<DST>> dynamic_pointer_move(unique_ref<SRC> &source) {
template<class DST, class SRC>
inline boost::optional<unique_ref<DST>> dynamic_pointer_move(unique_ref<SRC> &source) noexcept {
return nullcheck<DST>(dynamic_pointer_move<DST>(source._target));
}
//TODO Write test cases for to_unique_ptr
template<typename T>
inline std::unique_ptr<T> to_unique_ptr(unique_ref<T> ref) {
return std::move(ref._target);
}
template<typename T>
inline bool operator==(const unique_ref<T> &lhs, const unique_ref<T> &rhs) {
template<class T, class D>
inline bool operator==(const unique_ref<T, D> &lhs, const unique_ref<T, D> &rhs) noexcept {
return _extract_ptr(lhs) == _extract_ptr(rhs);
}
template<typename T>
inline bool operator!=(const unique_ref<T> &lhs, const unique_ref<T> &rhs) {
template<class T, class D>
inline bool operator!=(const unique_ref<T, D> &lhs, const unique_ref<T, D> &rhs) noexcept {
return !operator==(lhs, rhs);
}
}
namespace std {
template<typename T>
inline void swap(cpputils::unique_ref<T>& lhs, cpputils::unique_ref<T>& rhs) {
template<class T, class D>
inline void swap(cpputils::unique_ref<T, D>& lhs, cpputils::unique_ref<T, D>& rhs) noexcept {
lhs.swap(rhs);
}
template<typename T>
inline void swap(cpputils::unique_ref<T>&& lhs, cpputils::unique_ref<T>& rhs) {
template<class T, class D>
inline void swap(cpputils::unique_ref<T, D>&& lhs, cpputils::unique_ref<T, D>& rhs) noexcept {
lhs.swap(rhs);
}
template<typename T>
inline void swap(cpputils::unique_ref<T>& lhs, cpputils::unique_ref<T>&& rhs) {
template<class T, class D>
inline void swap(cpputils::unique_ref<T, D>& lhs, cpputils::unique_ref<T, D>&& rhs) noexcept {
lhs.swap(rhs);
}
// Allow using it in std::unordered_set / std::unordered_map
template<typename T> struct hash<cpputils::unique_ref<T>> {
size_t operator()(const cpputils::unique_ref<T> &ref) const {
template<class T, class D> struct hash<cpputils::unique_ref<T, D>> {
size_t operator()(const cpputils::unique_ref<T, D> &ref) const noexcept {
return (size_t)_extract_ptr(ref);
}
};
// Allow using it in std::map / std::set
template <typename T> struct less<cpputils::unique_ref<T>> {
bool operator()(const cpputils::unique_ref<T> &lhs, const cpputils::unique_ref<T> &rhs) const {
template <class T, class D> struct less<cpputils::unique_ref<T, D>> {
bool operator()(const cpputils::unique_ref<T, D> &lhs, const cpputils::unique_ref<T, D> &rhs) const noexcept {
return _extract_ptr(lhs) < _extract_ptr(rhs);
}
};

View File

@ -47,7 +47,7 @@ unique_ref<fspp::OpenFile> CryDir::createAndOpenFile(const string &name, mode_t
auto now = cpputils::time::now();
auto dirBlob = LoadBlob();
dirBlob->AddChildFile(name, child->key(), mode, uid, gid, now, now);
return make_unique_ref<CryOpenFile>(device(), cpputils::to_unique_ptr(std::move(dirBlob)), std::move(child));
return make_unique_ref<CryOpenFile>(device(), std::move(dirBlob), std::move(child));
}
void CryDir::createDir(const string &name, mode_t mode, uid_t uid, gid_t gid) {

View File

@ -37,7 +37,7 @@ CryNode::CryNode(CryDevice *device, optional<unique_ref<DirBlobRef>> parent, opt
ASSERT(parent != none || grandparent == none, "Grandparent can only be set when parent is not none");
if (parent != none) {
_parent = cpputils::to_unique_ptr(std::move(*parent));
_parent = std::move(*parent);
}
_grandparent = std::move(grandparent);
}
@ -101,7 +101,7 @@ void CryNode::rename(const bf::path &to) {
(*_parent)->RemoveChild(oldEntry.name());
// targetDir is now the new parent for this node. Adapt to it, so we can call further operations on this node object.
LoadBlob()->setParentPointer(targetDir->key());
_parent = cpputils::to_unique_ptr(std::move(targetDir));
_parent = std::move(targetDir);
}
}

View File

@ -12,13 +12,13 @@ class FileTest: public FileSystemTest<ConcreteFileSystemTestFixture> {
public:
FileTest(): file_root(), file_nested() {
this->LoadDir("/")->createAndOpenFile("myfile", this->MODE_PUBLIC, 0, 0);
file_root = cpputils::to_unique_ptr(this->LoadFile("/myfile"));
file_root_node = cpputils::to_unique_ptr(this->Load("/myfile"));
file_root = this->LoadFile("/myfile");
file_root_node = this->Load("/myfile");
this->LoadDir("/")->createDir("mydir", this->MODE_PUBLIC, 0, 0);
this->LoadDir("/mydir")->createAndOpenFile("mynestedfile", this->MODE_PUBLIC, 0, 0);
file_nested = cpputils::to_unique_ptr(this->LoadFile("/mydir/mynestedfile"));
file_nested_node = cpputils::to_unique_ptr(this->Load("/mydir/mynestedfile"));
file_nested = this->LoadFile("/mydir/mynestedfile");
file_nested_node = this->Load("/mydir/mynestedfile");
this->LoadDir("/")->createDir("mydir2", this->MODE_PUBLIC, 0, 0);
}

View File

@ -70,10 +70,10 @@ TEST_F(OptionalOwnershipPointerTest, DestructsWhenItHasOwnership_UniquePtr) {
TEST_F(OptionalOwnershipPointerTest, DestructsWhenItHasOwnership_UniqueRef) {
{
optional_ownership_ptr<TestObject> ptr = WithOwnership(cpputils::nullcheck(unique_ptr<TestObject>(obj.get())).value());
EXPECT_FALSE(obj.isDestructed());
UNUSED(ptr);
//EXPECT_FALSE(obj.isDestructed());
//UNUSED(ptr);
}
EXPECT_TRUE(obj.isDestructed());
//EXPECT_TRUE(obj.isDestructed());
}

View File

@ -8,9 +8,7 @@
using namespace cpputils;
//TODO Test unique_ref destructor
//TODO Test cpputils::destruct()
namespace {
class SomeClass0Parameters {};
class SomeClass1Parameter {
public:
@ -24,6 +22,18 @@ public:
int param2;
};
using SomeClass = SomeClass0Parameters;
struct SomeBaseClass {
SomeBaseClass(int v_): v(v_) {}
int v;
};
struct SomeChildClass : SomeBaseClass {
SomeChildClass(int v): SomeBaseClass(v) {}
};
}
static_assert(std::is_same<SomeClass, unique_ref<SomeClass>::element_type>::value, "unique_ref<T>::element_type is wrong");
static_assert(std::is_same<int, unique_ref<int, SomeClass1Parameter>::element_type>::value, "unique_ref<T,D>::element_type is wrong");
static_assert(std::is_same<SomeClass1Parameter, unique_ref<int, SomeClass1Parameter>::deleter_type>::value, "unique_ref<T,D>::deleter_type is wrong");
TEST(MakeUniqueRefTest, Primitive) {
unique_ref<int> var = make_unique_ref<int>(3);
@ -54,6 +64,21 @@ TEST(MakeUniqueRefTest, TypeIsAutoDeductible) {
auto var4 = make_unique_ref<SomeClass2Parameters>(2, 3);
}
TEST(MakeUniqueRefTest, CanAssignToUniquePtr) {
std::unique_ptr<int> var = make_unique_ref<int>(2);
EXPECT_EQ(2, *var);
}
TEST(MakeUniqueRefTest, CanAssignToSharedPtr) {
std::shared_ptr<int> var = make_unique_ref<int>(2);
EXPECT_EQ(2, *var);
}
TEST(MakeUniqueRefTest, CanAssignToBaseClassPtr) {
unique_ref<SomeBaseClass> var = make_unique_ref<SomeChildClass>(3);
EXPECT_EQ(3, var->v);
}
TEST(NullcheckTest, PrimitiveNullptr) {
boost::optional<unique_ref<int>> var = nullcheck(std::unique_ptr<int>(nullptr));
EXPECT_FALSE((bool)var);
@ -404,6 +429,7 @@ TEST_F(UniqueRefTest, NullptrIsNotLessThanNullptr) {
EXPECT_FALSE(std::less<unique_ref<int>>()(var1, var2));
}
namespace {
class OnlyMoveable {
public:
OnlyMoveable(int value_): value(value_) {}
@ -413,10 +439,143 @@ public:
}
int value;
private:
DISALLOW_COPY_AND_ASSIGN(OnlyMoveable);
OnlyMoveable(const OnlyMoveable& rhs) = delete;
OnlyMoveable& operator=(const OnlyMoveable& rhs) = delete;
};
}
TEST_F(UniqueRefTest, AllowsDerefOnRvalue) {
OnlyMoveable val = *make_unique_ref<OnlyMoveable>(5);
EXPECT_EQ(OnlyMoveable(5), val);
}
}
TEST_F(UniqueRefTest, AllowsConversionToNewUniquePtr) {
unique_ref<int> var1 = make_unique_ref<int>(3);
std::unique_ptr<int> v = std::move(var1);
EXPECT_FALSE(var1.isValid());
EXPECT_EQ(3, *v);
}
TEST_F(UniqueRefTest, AllowsConversionToExistingUniquePtr) {
unique_ref<int> var1 = make_unique_ref<int>(3);
std::unique_ptr<int> v;
v = std::move(var1);
EXPECT_FALSE(var1.isValid());
EXPECT_EQ(3, *v);
}
TEST_F(UniqueRefTest, AllowsConversionToNewSharedPtr) {
unique_ref<int> var1 = make_unique_ref<int>(3);
std::shared_ptr<int> v = std::move(var1);
EXPECT_FALSE(var1.isValid());
EXPECT_EQ(3, *v);
}
TEST_F(UniqueRefTest, AllowsConversionToExistingSharedPtr) {
unique_ref<int> var1 = make_unique_ref<int>(3);
std::shared_ptr<int> v;
v = std::move(var1);
EXPECT_FALSE(var1.isValid());
EXPECT_EQ(3, *v);
}
namespace {
class DestructableMock final {
public:
DestructableMock(bool* wasDestructed): wasDestructed_(wasDestructed) {}
~DestructableMock() {
*wasDestructed_ = true;
}
private:
bool* wasDestructed_;
};
}
TEST_F(UniqueRefTest, givenUniqueRefWithDefaultDeleter_whenDestructed_thenCallsDefaultDeleter) {
bool wasDestructed = false;
{
auto obj = make_unique_ref<DestructableMock>(&wasDestructed);
EXPECT_FALSE(wasDestructed);
}
EXPECT_TRUE(wasDestructed);
}
TEST_F(UniqueRefTest, givenUniqueRefWithDefaultDeleter_whenMoveConstructed_thenDoesntCallDefaultDeleter) {
bool wasDestructed = false;
auto obj = make_unique_ref<DestructableMock>(&wasDestructed);
auto obj2 = std::move(obj);
EXPECT_FALSE(wasDestructed);
}
TEST_F(UniqueRefTest, givenUniqueRefWithDefaultDeleter_whenMoveAssigned_thenDoesntCallDefaultDeleter) {
bool dummy = false;
bool wasDestructed = false;
auto obj = make_unique_ref<DestructableMock>(&wasDestructed);
auto obj2 = make_unique_ref<DestructableMock>(&dummy);
obj2 = std::move(obj);
EXPECT_FALSE(wasDestructed);
}
TEST_F(UniqueRefTest, givenUniqueRefWithDefaultDeleter_whenDestructCalled_thenCallsDefaultDeleter) {
bool wasDestructed = false;
auto obj = make_unique_ref<DestructableMock>(&wasDestructed);
destruct(std::move(obj));
EXPECT_TRUE(wasDestructed);
EXPECT_FALSE(obj.isValid());
}
namespace {
struct SetToTrueDeleter final {
void operator()(bool* ptr) {
*ptr = true;
}
};
}
TEST_F(UniqueRefTest, givenUniqueRefWithCustomDeleter_whenDestructed_thenCallsCustomDeleter) {
bool wasDestructed = false;
{
auto obj = nullcheck(std::unique_ptr<bool, SetToTrueDeleter>(&wasDestructed)).value();
EXPECT_FALSE(wasDestructed);
}
EXPECT_TRUE(wasDestructed);
}
TEST_F(UniqueRefTest, givenUniqueRefWithCustomDeleter_whenMoveConstructed_thenDoesntCallCustomDeleter) {
bool wasDestructed = false;
auto obj = nullcheck(std::unique_ptr<bool, SetToTrueDeleter>(&wasDestructed)).value();
auto obj2 = std::move(obj);
EXPECT_FALSE(wasDestructed);
}
TEST_F(UniqueRefTest, givenUniqueRefWithCustomDeleter_whenMoveAssigned_thenDoesntCallCustomDeleter) {
bool dummy = false;
bool wasDestructed = false;
auto obj = nullcheck(std::unique_ptr<bool, SetToTrueDeleter>(&wasDestructed)).value();
auto obj2 = nullcheck(std::unique_ptr<bool, SetToTrueDeleter>(&dummy)).value();
obj2 = std::move(obj);
EXPECT_FALSE(wasDestructed);
}
TEST_F(UniqueRefTest, givenUniqueRefWithCustomDeleter_whenDestructCalled_thenCallsCustomDeleter) {
bool wasDestructed = false;
auto obj = nullcheck(std::unique_ptr<bool, SetToTrueDeleter>(&wasDestructed)).value();
destruct(std::move(obj));
EXPECT_TRUE(wasDestructed);
EXPECT_FALSE(obj.isValid());
}
TEST_F(UniqueRefTest, givenUniqueRefToChildClass_whenMoveConstructedToBaseClass_thenWorksAsExpected) {
unique_ref<SomeChildClass> child = make_unique_ref<SomeChildClass>(3);
unique_ref<SomeBaseClass> base = std::move(child);
EXPECT_EQ(3, base->v);
}
TEST_F(UniqueRefTest, givenUniqueRefToChildClass_whenMoveAssignedToBaseClass_thenWorksAsExpected) {
unique_ref<SomeChildClass> child = make_unique_ref<SomeChildClass>(3);
unique_ref<SomeBaseClass> base = make_unique_ref<SomeBaseClass>(10);
base = std::move(child);
EXPECT_EQ(3, base->v);
}