[K/N] Support CF objects in ObjectPtr ^KT-63423

This commit is contained in:
Alexander Shabalin
2024-01-10 13:14:02 +01:00
committed by Space Team
parent e5269daa74
commit 2dd7a9ef21
3 changed files with 306 additions and 189 deletions
@@ -11,6 +11,7 @@
#error "Assumes that ARC is not used"
#endif
#include <CoreFoundation/CFBase.h>
#include <functional>
#include <utility>
@@ -20,35 +21,35 @@ OBJC_FORWARD_DECLARE(NSObject);
namespace kotlin::objc_support {
struct object_ptr_retain_t{};
struct object_ptr_retain_t {};
inline constexpr object_ptr_retain_t object_ptr_retain{};
namespace internal {
class ObjectPtrImpl {
class NSObjectPtrImpl {
public:
ObjectPtrImpl() noexcept;
explicit ObjectPtrImpl(NSObject* object) noexcept;
ObjectPtrImpl(object_ptr_retain_t, NSObject* object) noexcept;
NSObjectPtrImpl() noexcept;
explicit NSObjectPtrImpl(NSObject* object) noexcept;
NSObjectPtrImpl(object_ptr_retain_t, NSObject* object) noexcept;
ObjectPtrImpl(const ObjectPtrImpl& rhs) noexcept;
ObjectPtrImpl(ObjectPtrImpl&& rhs) noexcept;
NSObjectPtrImpl(const NSObjectPtrImpl& rhs) noexcept;
NSObjectPtrImpl(NSObjectPtrImpl&& rhs) noexcept;
~ObjectPtrImpl();
~NSObjectPtrImpl();
ObjectPtrImpl& operator=(const ObjectPtrImpl& rhs) noexcept {
ObjectPtrImpl tmp(rhs);
NSObjectPtrImpl& operator=(const NSObjectPtrImpl& rhs) noexcept {
NSObjectPtrImpl tmp(rhs);
swap(tmp);
return *this;
}
ObjectPtrImpl& operator=(ObjectPtrImpl&& rhs) noexcept {
ObjectPtrImpl tmp(std::move(rhs));
NSObjectPtrImpl& operator=(NSObjectPtrImpl&& rhs) noexcept {
NSObjectPtrImpl tmp(std::move(rhs));
swap(tmp);
return *this;
}
void swap(ObjectPtrImpl& rhs) noexcept;
void swap(NSObjectPtrImpl& rhs) noexcept;
NSObject* get() const noexcept;
bool valid() const noexcept;
@@ -57,20 +58,60 @@ public:
void reset(NSObject* object) noexcept;
void reset(object_ptr_retain_t, NSObject* object) noexcept;
bool operator==(const ObjectPtrImpl& rhs) const noexcept;
bool operator<(const ObjectPtrImpl& rhs) const noexcept;
std::size_t computeHash() const noexcept;
bool operator==(const NSObjectPtrImpl& rhs) const noexcept;
bool operator<(const NSObjectPtrImpl& rhs) const noexcept;
private:
NSObject* object_;
};
class CFObjectPtrImpl {
public:
CFObjectPtrImpl() noexcept : object_(nullptr) {}
explicit CFObjectPtrImpl(CFTypeRef object) noexcept : object_(object) {}
CFObjectPtrImpl(object_ptr_retain_t, CFTypeRef object) noexcept : object_(CFRetain(object)) {}
CFObjectPtrImpl(const CFObjectPtrImpl& rhs) noexcept : object_(CFRetain(rhs.object_)) {}
CFObjectPtrImpl(CFObjectPtrImpl&& rhs) noexcept : object_(rhs.object_) { rhs.object_ = nullptr; }
~CFObjectPtrImpl();
CFObjectPtrImpl& operator=(const CFObjectPtrImpl& rhs) noexcept {
CFObjectPtrImpl tmp(rhs);
swap(tmp);
return *this;
}
CFObjectPtrImpl& operator=(CFObjectPtrImpl&& rhs) noexcept {
CFObjectPtrImpl tmp(std::move(rhs));
swap(tmp);
return *this;
}
void swap(CFObjectPtrImpl& rhs) noexcept {
using std::swap;
swap(object_, rhs.object_);
}
CFTypeRef get() const noexcept { return object_; }
bool valid() const noexcept { return object_ != nullptr; }
void reset() noexcept { reset(nullptr); }
void reset(CFTypeRef object) noexcept;
void reset(object_ptr_retain_t, CFTypeRef object) noexcept { reset(CFRetain(object)); }
bool operator==(const CFObjectPtrImpl& rhs) const noexcept { return object_ == rhs.object_; }
bool operator<(const CFObjectPtrImpl& rhs) const noexcept { return std::less<>()(object_, rhs.object_); }
private:
CFTypeRef object_;
};
} // namespace internal
// `std::shared_ptr`-like smart pointer for ObjC objects.
// `std::shared_ptr`-like smart pointer for ObjC/CF objects.
//
// IMPORTANT: By default constructed from a retained ObjC object.
// IMPORTANT: By default constructed from a retained ObjC/CF object.
// This optimizes for the common case of constructing directly from `[[T alloc] init...]`.
// To construct and additionally retain the object, use `object_ptr_retain` as the first argument
// to the constructor.
@@ -86,23 +127,28 @@ private:
// When transfering ownership, prefer to move because it avoids calling `retain` and `release`.
template <typename T>
class object_ptr {
// All CF types are known only as CF...Ref which are always pointers.
static inline constexpr bool useCF = std::is_pointer_v<T>;
public:
using element_type = std::conditional_t<useCF, std::remove_pointer_t<T>, T>;
// Construct empty `object_ptr`.
object_ptr() noexcept = default;
// Construct from ObjC object.
// Construct from ObjC/CF object.
//
// IMPORTANT: this does not `retain` `ptr`.
explicit object_ptr(T* ptr) noexcept : impl_(ptr) {}
explicit object_ptr(element_type* ptr) noexcept : impl_(ptr) {}
// Construct from ObjC object, additionally retaining `ptr`.
object_ptr(object_ptr_retain_t, T* ptr) noexcept : impl_(object_ptr_retain, ptr) {}
// Construct from ObjC/CF object, additionally retaining `ptr`.
object_ptr(object_ptr_retain_t, element_type* ptr) noexcept : impl_(object_ptr_retain, ptr) {}
void swap(object_ptr<T>& rhs) noexcept { impl_.swap(rhs.impl_); }
T* get() const noexcept { return (T*)impl_.get(); }
T* operator*() const noexcept { return (T*)impl_.get(); }
T* operator->() const noexcept { return (T*)impl_.get(); }
element_type* get() const noexcept { return (element_type*)impl_.get(); }
element_type* operator*() const noexcept { return (element_type*)impl_.get(); }
element_type* operator->() const noexcept { return (element_type*)impl_.get(); }
explicit operator bool() const noexcept { return impl_.valid(); }
@@ -112,10 +158,10 @@ public:
// Release stored pointer and store `ptr` instead.
//
// IMPORTANT: this does not `retain` `ptr`.
void reset(T* ptr) noexcept { impl_.reset(ptr); }
void reset(element_type* ptr) noexcept { impl_.reset(ptr); }
// Release stored pointer and store `ptr` instead, additionally retaining `ptr`.
void reset(object_ptr_retain_t, T* ptr) noexcept { impl_.reset(object_ptr_retain, ptr); }
void reset(object_ptr_retain_t, element_type* ptr) noexcept { impl_.reset(object_ptr_retain, ptr); }
template <typename U>
bool operator==(const object_ptr<U>& rhs) const noexcept {
@@ -133,7 +179,7 @@ private:
template <typename U>
friend class object_ptr;
internal::ObjectPtrImpl impl_;
std::conditional_t<useCF, internal::CFObjectPtrImpl, internal::NSObjectPtrImpl> impl_;
};
template <typename T, typename U>
@@ -167,7 +213,7 @@ void swap(kotlin::objc_support::object_ptr<T>& lhs, kotlin::objc_support::object
template <typename T>
struct hash<kotlin::objc_support::object_ptr<T>> {
std::size_t operator()(const kotlin::objc_support::object_ptr<T>& value) { return std::hash<NSObject*>()(value.impl_.get()); }
std::size_t operator()(const kotlin::objc_support::object_ptr<T>& value) { return std::hash<const void*>()(value.impl_.get()); }
};
// TODO: std::atomic specialization?
@@ -11,62 +11,75 @@
using namespace kotlin;
objc_support::internal::ObjectPtrImpl::ObjectPtrImpl() noexcept : object_(nil) {}
objc_support::internal::NSObjectPtrImpl::NSObjectPtrImpl() noexcept : object_(nil) {}
objc_support::internal::ObjectPtrImpl::ObjectPtrImpl(NSObject* object) noexcept : object_(object) {}
objc_support::internal::NSObjectPtrImpl::NSObjectPtrImpl(NSObject* object) noexcept : object_(object) {}
objc_support::internal::ObjectPtrImpl::ObjectPtrImpl(object_ptr_retain_t, NSObject* object) noexcept : object_([object retain]) {}
objc_support::internal::NSObjectPtrImpl::NSObjectPtrImpl(object_ptr_retain_t, NSObject* object) noexcept : object_([object retain]) {}
objc_support::internal::ObjectPtrImpl::ObjectPtrImpl(const ObjectPtrImpl& rhs) noexcept : object_([rhs.object_ retain]) {}
objc_support::internal::NSObjectPtrImpl::NSObjectPtrImpl(const NSObjectPtrImpl& rhs) noexcept : object_([rhs.object_ retain]) {}
objc_support::internal::ObjectPtrImpl::ObjectPtrImpl(ObjectPtrImpl&& rhs) noexcept : object_(rhs.object_) {
objc_support::internal::NSObjectPtrImpl::NSObjectPtrImpl(NSObjectPtrImpl&& rhs) noexcept : object_(rhs.object_) {
rhs.object_ = nil;
}
objc_support::internal::ObjectPtrImpl::~ObjectPtrImpl() {
objc_support::internal::NSObjectPtrImpl::~NSObjectPtrImpl() {
@autoreleasepool {
[object_ release];
}
}
void objc_support::internal::ObjectPtrImpl::swap(ObjectPtrImpl& rhs) noexcept {
void objc_support::internal::NSObjectPtrImpl::swap(NSObjectPtrImpl& rhs) noexcept {
using std::swap;
swap(object_, rhs.object_);
}
NSObject* objc_support::internal::ObjectPtrImpl::get() const noexcept {
NSObject* objc_support::internal::NSObjectPtrImpl::get() const noexcept {
return object_;
}
bool objc_support::internal::ObjectPtrImpl::valid() const noexcept {
bool objc_support::internal::NSObjectPtrImpl::valid() const noexcept {
return object_ != nil;
}
void objc_support::internal::ObjectPtrImpl::reset() noexcept {
void objc_support::internal::NSObjectPtrImpl::reset() noexcept {
reset(nil);
}
void objc_support::internal::ObjectPtrImpl::reset(NSObject* object) noexcept {
void objc_support::internal::NSObjectPtrImpl::reset(NSObject* object) noexcept {
@autoreleasepool {
[object_ release];
object_ = object;
}
}
void objc_support::internal::ObjectPtrImpl::reset(object_ptr_retain_t, NSObject* object) noexcept {
void objc_support::internal::NSObjectPtrImpl::reset(object_ptr_retain_t, NSObject* object) noexcept {
reset([object retain]);
}
std::size_t objc_support::internal::ObjectPtrImpl::computeHash() const noexcept {
return object_.hash;
}
bool objc_support::internal::ObjectPtrImpl::operator==(const ObjectPtrImpl& rhs) const noexcept {
bool objc_support::internal::NSObjectPtrImpl::operator==(const NSObjectPtrImpl& rhs) const noexcept {
return object_ == rhs.object_;
}
bool objc_support::internal::ObjectPtrImpl::operator<(const ObjectPtrImpl& rhs) const noexcept {
bool objc_support::internal::NSObjectPtrImpl::operator<(const NSObjectPtrImpl& rhs) const noexcept {
return std::less<>()(object_, rhs.object_);
}
objc_support::internal::CFObjectPtrImpl::~CFObjectPtrImpl() {
if (object_) {
@autoreleasepool {
CFRelease(object_);
}
}
}
void objc_support::internal::CFObjectPtrImpl::reset(CFTypeRef object) noexcept {
if (object_) {
@autoreleasepool {
CFRelease(object_);
}
}
object_ = object;
}
#endif
@@ -7,6 +7,8 @@
#include "ObjectPtr.hpp"
#import <CoreFoundation/CFArray.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSObject.h>
#include <functional>
#include <memory>
@@ -61,145 +63,201 @@ private:
@end
template <typename T>
class ObjectPtrTest : public testing::Test {};
struct ObjectPtrTestCF {
using TestedType = objc_support::object_ptr<CFArrayRef>;
using OtherType = objc_support::object_ptr<CFTypeRef>;
static CFArrayRef withDestructorHook(std::function<DestructorHook> hook) noexcept {
auto* contents = [[WithDestructorHookObjC alloc] initWithDestructorHook:std::move(hook)];
auto* array = @[contents];
[contents release];
return (__bridge CFArrayRef)array;
}
static WithDestructorHook* getHook(CFArrayRef ptr) noexcept {
auto* array = (__bridge NSArray<WithDestructorHookObjC*>*)ptr;
return array[0].impl;
}
static void release(CFArrayRef ptr) noexcept { CFRelease(ptr); }
static CFArrayRef retain(CFArrayRef ptr) noexcept {
CFRetain(ptr);
return ptr;
}
};
struct ObjectPtrTestNS {
using TestedType = objc_support::object_ptr<WithDestructorHookObjC>;
using OtherType = objc_support::object_ptr<NSObject>;
static WithDestructorHookObjC* withDestructorHook(std::function<DestructorHook> hook) noexcept {
return [[WithDestructorHookObjC alloc] initWithDestructorHook:std::move(hook)];
}
static WithDestructorHook* getHook(WithDestructorHookObjC* ptr) noexcept { return ptr.impl; }
static void release(WithDestructorHookObjC* ptr) noexcept { [ptr release]; }
static WithDestructorHookObjC* retain(WithDestructorHookObjC* ptr) noexcept { return [ptr retain]; }
};
using ObjectPtrTestTypes = testing::Types<ObjectPtrTestCF, ObjectPtrTestNS>;
class ObjectPtrTestNames {
public:
template <typename T>
static std::string GetName(int) {
if constexpr (std::is_same_v<T, ObjectPtrTestCF>) {
return "CF";
} else if constexpr (std::is_same_v<T, ObjectPtrTestNS>) {
return "NS";
}
}
};
TYPED_TEST_SUITE(ObjectPtrTest, ObjectPtrTestTypes, ObjectPtrTestNames);
#define EXPECT_CONTAINS(smartPtr, ptr) \
do { \
EXPECT_THAT(static_cast<bool>(smartPtr), (ptr) != nil); \
EXPECT_THAT(static_cast<bool>(smartPtr), (ptr) != nullptr); \
EXPECT_THAT(*(smartPtr), (ptr)); \
EXPECT_THAT((smartPtr).get(), (ptr)); \
} while (false)
TEST(ObjectPtrTest, DefaultCtor) {
objc_support::object_ptr<WithDestructorHookObjC> obj;
TYPED_TEST(ObjectPtrTest, DefaultCtor) {
typename TypeParam::TestedType obj;
EXPECT_CONTAINS(obj, nil);
}
TEST(ObjectPtrTest, ObjectCtor) {
TYPED_TEST(ObjectPtrTest, ObjectCtor) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
{
objc_support::object_ptr<WithDestructorHookObjC> obj(ptr);
typename TypeParam::TestedType obj(ptr);
EXPECT_CONTAINS(obj, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr)));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, ObjectCtorWithRetain) {
TYPED_TEST(ObjectPtrTest, ObjectCtorWithRetain) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
{
objc_support::object_ptr<WithDestructorHookObjC> obj(objc_support::object_ptr_retain, ptr);
typename TypeParam::TestedType obj(objc_support::object_ptr_retain, ptr);
EXPECT_CONTAINS(obj, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl)).Times(0);
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr))).Times(0);
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CALL(destructorHook, Call(ptr.impl));
[ptr release];
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr)));
TypeParam::release(ptr);
}
TEST(ObjectPtrTest, CopyCtor) {
TYPED_TEST(ObjectPtrTest, CopyCtor) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
{
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr);
objc_support::object_ptr<WithDestructorHookObjC> obj2(obj1);
typename TypeParam::TestedType obj1(ptr);
typename TypeParam::TestedType obj2(obj1);
EXPECT_CONTAINS(obj1, ptr);
EXPECT_CONTAINS(obj2, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl)).Times(0);
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr))).Times(0);
obj1.reset();
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj1, nil);
EXPECT_CONTAINS(obj2, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr)));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, MoveCtor) {
TYPED_TEST(ObjectPtrTest, MoveCtor) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
{
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr);
typename TypeParam::TestedType obj1(ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl)).Times(0);
objc_support::object_ptr<WithDestructorHookObjC> obj2(std::move(obj1));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr))).Times(0);
typename TypeParam::TestedType obj2(std::move(obj1));
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj1, nil);
EXPECT_CONTAINS(obj2, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr)));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, CopyAssignmentIntoEmpty) {
TYPED_TEST(ObjectPtrTest, CopyAssignmentIntoEmpty) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
{
objc_support::object_ptr<WithDestructorHookObjC> obj2;
typename TypeParam::TestedType obj2;
{
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr);
typename TypeParam::TestedType obj1(ptr);
obj2 = obj1;
EXPECT_CONTAINS(obj1, ptr);
EXPECT_CONTAINS(obj2, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl)).Times(0);
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr))).Times(0);
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj2, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr)));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, CopyAssignmentIntoSet) {
TYPED_TEST(ObjectPtrTest, CopyAssignmentIntoSet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr1 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
auto* ptr2 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
{
objc_support::object_ptr<WithDestructorHookObjC> obj2(ptr2);
typename TypeParam::TestedType obj2(ptr2);
{
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr1);
typename TypeParam::TestedType obj1(ptr1);
EXPECT_CALL(destructorHook, Call(ptr2.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr2)));
obj2 = obj1;
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj1, ptr1);
EXPECT_CONTAINS(obj2, ptr1);
EXPECT_CALL(destructorHook, Call(ptr1.impl)).Times(0);
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr1))).Times(0);
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj2, ptr1);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr1)));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, MoveAssignmentIntoEmpty) {
TYPED_TEST(ObjectPtrTest, MoveAssignmentIntoEmpty) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
{
objc_support::object_ptr<WithDestructorHookObjC> obj2;
typename TypeParam::TestedType obj2;
{
EXPECT_CALL(destructorHook, Call(ptr.impl)).Times(0);
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr))).Times(0);
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr);
typename TypeParam::TestedType obj1(ptr);
obj2 = std::move(obj1);
EXPECT_CONTAINS(obj1, nil);
EXPECT_CONTAINS(obj2, ptr);
@@ -208,23 +266,23 @@ TEST(ObjectPtrTest, MoveAssignmentIntoEmpty) {
EXPECT_CONTAINS(obj2, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr)));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, MoveAssignmentIntoSet) {
TYPED_TEST(ObjectPtrTest, MoveAssignmentIntoSet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr1 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
auto* ptr2 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
{
objc_support::object_ptr<WithDestructorHookObjC> obj2(ptr2);
typename TypeParam::TestedType obj2(ptr2);
{
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr1);
typename TypeParam::TestedType obj1(ptr1);
EXPECT_CALL(destructorHook, Call(ptr2.impl));
EXPECT_CALL(destructorHook, Call(ptr1.impl)).Times(0);
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr2)));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr1))).Times(0);
obj2 = std::move(obj1);
testing::Mock::VerifyAndClearExpectations(&destructorHook);
@@ -235,142 +293,142 @@ TEST(ObjectPtrTest, MoveAssignmentIntoSet) {
EXPECT_CONTAINS(obj2, ptr1);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr1)));
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
}
TEST(ObjectPtrTest, SwapEmptyEmpty) {
objc_support::object_ptr<WithDestructorHookObjC> obj1;
objc_support::object_ptr<WithDestructorHookObjC> obj2;
TYPED_TEST(ObjectPtrTest, SwapEmptyEmpty) {
typename TypeParam::TestedType obj1;
typename TypeParam::TestedType obj2;
obj1.swap(obj2);
EXPECT_CONTAINS(obj1, nil);
EXPECT_CONTAINS(obj2, nil);
}
TEST(ObjectPtrTest, SwapEmptySet) {
TYPED_TEST(ObjectPtrTest, SwapEmptySet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj1;
objc_support::object_ptr<WithDestructorHookObjC> obj2(ptr2);
auto* ptr2 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
typename TypeParam::TestedType obj1;
typename TypeParam::TestedType obj2(ptr2);
obj1.swap(obj2);
EXPECT_CONTAINS(obj1, ptr2);
EXPECT_CONTAINS(obj2, nil);
EXPECT_CALL(destructorHook, Call(ptr2.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr2)));
}
TEST(ObjectPtrTest, SwapSetEmpty) {
TYPED_TEST(ObjectPtrTest, SwapSetEmpty) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr1);
objc_support::object_ptr<WithDestructorHookObjC> obj2;
auto* ptr1 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
typename TypeParam::TestedType obj1(ptr1);
typename TypeParam::TestedType obj2;
obj1.swap(obj2);
EXPECT_CONTAINS(obj1, nil);
EXPECT_CONTAINS(obj2, ptr1);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr1)));
}
TEST(ObjectPtrTest, SwapSetSet) {
TYPED_TEST(ObjectPtrTest, SwapSetSet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr1);
objc_support::object_ptr<WithDestructorHookObjC> obj2(ptr2);
auto* ptr1 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
auto* ptr2 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
typename TypeParam::TestedType obj1(ptr1);
typename TypeParam::TestedType obj2(ptr2);
obj1.swap(obj2);
EXPECT_CONTAINS(obj1, ptr2);
EXPECT_CONTAINS(obj2, ptr1);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
EXPECT_CALL(destructorHook, Call(ptr2.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr1)));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr2)));
}
TEST(ObjectPtrTest, ResetEmptyFromEmpty) {
objc_support::object_ptr<WithDestructorHookObjC> obj;
TYPED_TEST(ObjectPtrTest, ResetEmptyFromEmpty) {
typename TypeParam::TestedType obj;
obj.reset();
EXPECT_CONTAINS(obj, nil);
}
TEST(ObjectPtrTest, ResetEmptyFromSet) {
TYPED_TEST(ObjectPtrTest, ResetEmptyFromSet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj(ptr);
auto* ptr = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
typename TypeParam::TestedType obj(ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr)));
obj.reset();
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj, nil);
}
TEST(ObjectPtrTest, ResetObjectFromEmpty) {
TYPED_TEST(ObjectPtrTest, ResetObjectFromEmpty) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj;
auto* ptr = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
typename TypeParam::TestedType obj;
obj.reset(ptr);
EXPECT_CONTAINS(obj, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr)));
}
TEST(ObjectPtrTest, ResetObjectFromEmptyWithRetain) {
TYPED_TEST(ObjectPtrTest, ResetObjectFromEmptyWithRetain) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
{
objc_support::object_ptr<WithDestructorHookObjC> obj;
typename TypeParam::TestedType obj;
obj.reset(objc_support::object_ptr_retain, ptr);
EXPECT_CONTAINS(obj, ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl)).Times(0);
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr))).Times(0);
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CALL(destructorHook, Call(ptr.impl));
[ptr release];
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr)));
TypeParam::release(ptr);
}
TEST(ObjectPtrTest, ResetObjectFromSet) {
TYPED_TEST(ObjectPtrTest, ResetObjectFromSet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj(ptr1);
auto* ptr1 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
auto* ptr2 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
typename TypeParam::TestedType obj(ptr1);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr1)));
obj.reset(ptr2);
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj, ptr2);
EXPECT_CALL(destructorHook, Call(ptr2.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr2)));
}
TEST(ObjectPtrTest, ResetObjectFromSetWithRetain) {
TYPED_TEST(ObjectPtrTest, ResetObjectFromSetWithRetain) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr1 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
auto* ptr2 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
{
objc_support::object_ptr<WithDestructorHookObjC> obj(ptr1);
typename TypeParam::TestedType obj(ptr1);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr1)));
obj.reset(objc_support::object_ptr_retain, ptr2);
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CONTAINS(obj, ptr2);
EXPECT_CALL(destructorHook, Call(ptr2.impl)).Times(0);
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr2))).Times(0);
}
testing::Mock::VerifyAndClearExpectations(&destructorHook);
EXPECT_CALL(destructorHook, Call(ptr2.impl));
[ptr2 release];
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr2)));
TypeParam::release(ptr2);
}
#define EXPECT_EQUAL(expr1, expr2) \
@@ -403,19 +461,19 @@ TEST(ObjectPtrTest, ResetObjectFromSetWithRetain) {
EXPECT_TRUE((expr1) >= (expr2)); \
} while (false)
TEST(ObjectPtrTest, ComparisonsEmpty) {
TYPED_TEST(ObjectPtrTest, ComparisonsEmpty) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> empty;
objc_support::object_ptr<WithDestructorHookObjC> reset(ptr);
EXPECT_CALL(destructorHook, Call(ptr.impl));
auto* ptr = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
typename TypeParam::TestedType empty;
typename TypeParam::TestedType reset(ptr);
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr)));
reset.reset();
testing::Mock::VerifyAndClearExpectations(&destructorHook);
objc_support::object_ptr<NSObject> emptyOtherType;
typename TypeParam::OtherType emptyOtherType;
EXPECT_EQUAL(empty, empty);
EXPECT_EQUAL(empty, objc_support::object_ptr<WithDestructorHookObjC>());
EXPECT_EQUAL(empty, typename TypeParam::TestedType());
EXPECT_EQUAL(empty, reset);
EXPECT_EQUAL(empty, emptyOtherType);
EXPECT_EQUAL(reset, empty);
@@ -423,20 +481,20 @@ TEST(ObjectPtrTest, ComparisonsEmpty) {
EXPECT_EQUAL(reset, emptyOtherType);
}
TEST(ObjectPtrTest, ComparisonsEmptySet) {
TYPED_TEST(ObjectPtrTest, ComparisonsEmptySet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr3 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj(ptr1);
objc_support::object_ptr<WithDestructorHookObjC> empty;
objc_support::object_ptr<WithDestructorHookObjC> reset(ptr2);
EXPECT_CALL(destructorHook, Call(ptr2.impl));
auto* ptr1 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
auto* ptr2 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
auto* ptr3 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
typename TypeParam::TestedType obj(ptr1);
typename TypeParam::TestedType empty;
typename TypeParam::TestedType reset(ptr2);
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr2)));
reset.reset();
testing::Mock::VerifyAndClearExpectations(&destructorHook);
objc_support::object_ptr<NSObject> emptyOtherType;
objc_support::object_ptr<WithDestructorHookObjC> objOtherType(ptr3);
typename TypeParam::OtherType emptyOtherType;
typename TypeParam::TestedType objOtherType(ptr3);
EXPECT_GREATER(obj, empty);
EXPECT_GREATER(obj, reset);
@@ -448,24 +506,24 @@ TEST(ObjectPtrTest, ComparisonsEmptySet) {
EXPECT_GREATER(objOtherType, empty);
EXPECT_GREATER(objOtherType, reset);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
EXPECT_CALL(destructorHook, Call(ptr3.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr1)));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr3)));
}
TEST(ObjectPtrTest, ComparisonsSetSet) {
TYPED_TEST(ObjectPtrTest, ComparisonsSetSet) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr1 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
auto* ptr2 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
if (ptr2 < ptr1) {
std::swap(ptr1, ptr2);
}
ASSERT_LT(ptr1, ptr2);
objc_support::object_ptr<WithDestructorHookObjC> obj1(ptr1);
objc_support::object_ptr<WithDestructorHookObjC> obj2(ptr2);
objc_support::object_ptr<WithDestructorHookObjC> obj3(obj1);
objc_support::object_ptr<NSObject> objOtherType([ptr1 retain]);
typename TypeParam::TestedType obj1(ptr1);
typename TypeParam::TestedType obj2(ptr2);
typename TypeParam::TestedType obj3(obj1);
typename TypeParam::OtherType objOtherType(TypeParam::retain(ptr1));
EXPECT_EQUAL(obj1, obj1);
EXPECT_LESS(obj1, obj2);
@@ -484,30 +542,30 @@ TEST(ObjectPtrTest, ComparisonsSetSet) {
EXPECT_EQUAL(objOtherType, obj3);
EXPECT_EQUAL(objOtherType, objOtherType);
EXPECT_CALL(destructorHook, Call(ptr1.impl));
EXPECT_CALL(destructorHook, Call(ptr2.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr1)));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr2)));
}
TEST(ObjectPtrTest, Hashing) {
TYPED_TEST(ObjectPtrTest, Hashing) {
testing::StrictMock<testing::MockFunction<DestructorHook>> destructorHook;
auto* ptr1 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
auto* ptr2 = [[WithDestructorHookObjC alloc] initWithDestructorHook:destructorHook.AsStdFunction()];
objc_support::object_ptr<WithDestructorHookObjC> obj(ptr1);
objc_support::object_ptr<WithDestructorHookObjC> empty;
objc_support::object_ptr<WithDestructorHookObjC> reset(ptr2);
EXPECT_CALL(destructorHook, Call(ptr2.impl));
auto* ptr1 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
auto* ptr2 = TypeParam::withDestructorHook(destructorHook.AsStdFunction());
typename TypeParam::TestedType obj(ptr1);
typename TypeParam::TestedType empty;
typename TypeParam::TestedType reset(ptr2);
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr2)));
reset.reset();
testing::Mock::VerifyAndClearExpectations(&destructorHook);
using Hash = std::hash<objc_support::object_ptr<WithDestructorHookObjC>>;
using HashImpl = std::hash<NSObject*>;
using Hash = std::hash<typename TypeParam::TestedType>;
using HashImpl = std::hash<const void*>;
EXPECT_THAT(Hash()(obj), HashImpl()(ptr1));
EXPECT_THAT(Hash()(empty), HashImpl()(nullptr));
EXPECT_THAT(Hash()(reset), HashImpl()(nullptr));
EXPECT_CALL(destructorHook, Call(ptr1.impl));
EXPECT_CALL(destructorHook, Call(TypeParam::getHook(ptr1)));
}
#endif