A moveable QScopedPointer, or a Qt and std cross-compatible unique_pointer

 Clash Royale CLAN TAG#URR8PPP
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
11
down vote
favorite
I'm a hobby programmer, so I've never been through a code review before (online or offline). That said, here goes:
Background
I use Qt extensively but have long wanted QScopedPointer to be a moveable type. Since the Qt devs don't plan to implement this, I started trying to implement it myself. This was several years ago. [Pause for laughter]
Purpose
My goal is to not simply add move semantics to QScopedPointer:
- To maintain as much compatibility with the Standard Library as reasonably possible by- adding a partial specialization of std::hashandstd::swap,
- declaring the expected type traits,
- and implementing all of the public member functions in std::unique_pointer.
 
- adding a partial specialization of 
- To also maintain compatibility with Qt, so that qe::UniquePointeracts as a drop-in replacement forQScopedPointer.
- Use C++11 and later where appropriate (e.g., explicit operator bool). Currently, it compiles with C++14 as a minimum.
Notes
I kept the Qt-style deleter to allow use of existing deleters for QScopedPointer. The lambdas and macros in test.h are my quick-and-dirty GTest stand-in.
Goals
- I want to make sure the fundamentals are sound. Despite testing, experienced eyeballs are good.
- I don't use Boost and don't have a lot of Standard Library experience. Did I miss any usingdeclarations? Is my partial specialization ofstd::hashcorrectly done?
- I'm least confident about the conversion functions (accepting qe::UniquePointer<U, Cleanup>). Have I made any obvious mistakes that may create edge cases?
- Of course, I may have missed something for which the test class simply does not test. Any insight there would be appreciated.
Code
uniquepointer.h
#ifndef QE_CORE_UNIQUEPOINTER_H
#define QE_CORE_UNIQUEPOINTER_H
#include <type_traits>
namespace qe 
namespace detail 
template <class T>
constexpr void assertCompleteType() noexcept
 using forced_complete_type = char[sizeof(T) ? 1 : -1];
 (void)sizeof(forced_complete_type);
template <class T, class U>
void assertConvertible()
 using convertible = char[sizeof(std::is_convertible<T, U>()) ? 1 : -1];
 (void)sizeof(convertible);
 // namespace detail
template <typename T>
struct DefaultDeleter
 static inline void cleanup(T *pointer)
 
 if (pointer) 
 qe::detail::assertCompleteType<T>();
 delete pointer;
 
 
;
template <class T, class Cleanup = DefaultDeleter<T>>
class UniquePointer
public:
 using this_type = UniquePointer<T, Cleanup>;
 using element_type = T;
 using pointer = std::add_pointer_t<T>;
 using const_pointer = std::add_const_t<pointer>;
 using reference = std::add_lvalue_reference_t<T>;
 using const_reference = std::add_const_t<reference>;
 using deleter_type = Cleanup;
 inline UniquePointer(pointer p = nullptr) noexcept : d(p) 
 inline UniquePointer(this_type && other) noexcept : d(other.take()) 
 template <class U>
 inline UniquePointer(UniquePointer<U, Cleanup> && other) noexcept(std::is_convertible<U, T>())
 : d(other.take())
 
 qe::detail::assertConvertible<U, T>();
 
 inline this_type &operator=(this_type &&other)
 
 if (*this != other)
 reset(other.take());
 return *this;
 
 template <class U>
 inline this_type &operator=(UniquePointer<U, Cleanup> &&other)
 noexcept(std::is_convertible<U, T>())
 
 qe::detail::assertConvertible<U, T>();
 if (*this != other)
 reset(other.take());
 return *this;
 
 UniquePointer(const this_type &) = delete;
 this_type &operator=(const this_type &) = delete;
 inline ~UniquePointer()
 
 reset(nullptr);
 
 pointer take() noexcept
 
 auto oldD = this->d;
 this->d = nullptr;
 return oldD;
 
 inline void reset(pointer other = nullptr)
 
 if (this->d == other)
 return;
 auto oldD = this->d;
 if (oldD)
 deleter_type::cleanup(oldD);
 this->d = other;
 
 inline pointer data() const noexcept return this->d; 
 inline pointer get() const noexcept return this->d; 
 inline pointer* addressOf() noexcept return &(this->d); 
 explicit operator bool() const noexcept return !isNull(); 
 inline bool operator!() const noexcept return isNull(); 
 inline reference operator*() const noexcept return *(this->d); 
 inline pointer operator->() const noexcept return this->d; 
 inline bool isNull() const noexcept return !(this->d); 
 inline void swap(UniquePointer &other) noexcept std::swap(this->d, other.d); 
 template <class U>
 void swap(UniquePointer<U, Cleanup> &other)
 noexcept(std::is_convertible<U, T>() && std::is_convertible<T, U>())
 
 qe::detail::assertConvertible<U, T>();
 qe::detail::assertConvertible<T, U>();
 std::swap(this->d, other.d);
 
 pointer release() noexcept return take(); 
private:
 pointer d;
;
template <class T, class... Args>
inline UniquePointer<T> makeUnique(Args && ...args)
 return UniquePointer<T>(new T(std::forward<Args>(args)...));
 //namespace qe
template <class T, class Cleanup>
inline bool operator==(const qe::UniquePointer<T, Cleanup> &lhs, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return lhs.data() == rhs.data();
template<class T, class Cleanup>
inline bool operator ==(const qe::UniquePointer<T, Cleanup> &lhs, std::nullptr_t) noexcept
 return lhs.isNull();
template<class T, class Cleanup>
inline bool operator ==(std::nullptr_t, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return rhs.isNull();
template <class T, class Cleanup>
inline bool operator!=(const qe::UniquePointer<T, Cleanup> &lhs, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return lhs.data() != rhs.data();
template <class T, class Cleanup>
inline bool operator !=(const qe::UniquePointer<T, Cleanup> &lhs, std::nullptr_t) noexcept
 return !lhs.data();
template <class T, class Cleanup>
inline bool operator !=(std::nullptr_t, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return !rhs.data();
namespace std 
template <class T, class Cleanup>
inline void swap(qe::UniquePointer<T, Cleanup> &lhs, qe::UniquePointer<T, Cleanup> &rhs) noexcept
 lhs.swap(rhs);
template <class T, class Cleanup>
struct hash<qe::UniquePointer<T, Cleanup>>
 using argument_type = qe::UniquePointer<T, Cleanup>;
 using result_type = std::size_t;
 inline result_type operator()(const argument_type & p) const noexcept
 
 return std::hash<T *>(p.data());
 
;
 //namespace std
#endif //QE_CORE_UNIQUEPOINTER_H
test.h
#ifndef QE_TEST_TEST_H
#define QE_TEST_TEST_H
#include <assert.h>
auto equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs == rhs) && (rhs == lhs);
;
auto not_equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs != rhs);
;
auto less_than_check = (auto && lhs, auto && rhs) -> bool
 return (lhs < rhs);
;
auto less_than_or_equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs <= rhs);
;
auto greater_than_check = (auto && lhs, auto && rhs) -> bool
 return (lhs > rhs);
;
auto greater_than_or_equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs >= rhs);
;
auto implicit_true_check = (auto && lhs) -> bool
 return lhs ? true : false;
;
auto implicit_false_check = (auto && lhs) -> bool
 return lhs ? false : true;
;
#define EXPECT_EQ(x, y) assert(equal_check(x, y))
#define EXPECT_NE(x, y) assert(not_equal_check(x, y))
#define EXPECT_LT(x, y) assert(less_than_check(x, y))
#define EXPECT_LE(x, y) assert(less_than_or_equal_check(x, y))
#define EXPECT_GT(x, y) assert(greater_than_check(x, y))
#define EXPECT_GE(x, y) assert(greater_than_or_equal_check(x, y))
#define EXPECT_TRUE(x) assert(implicit_true_check(x))
#define EXPECT_FALSE(x) assert(implicit_false_check(x))
#endif // QE_TEST_TEST_H
main.cpp
#include <vector>
#include "uniquepointer.h"
#include "test.h"
using namespace qe;
struct Struct1
 explicit Struct1(int aVal)
 : value(aVal)
 
 instances++;
 
 Struct1(const Struct1 &) = default;
 Struct1(Struct1 &&) = default;
 ~Struct1()
 
 instances--;
 
 Struct1 &operator =(const Struct1 &) = default;
 Struct1 &operator =(Struct1 &&) = default;
 void incr()
 
 value++;
 
 void decr()
 
 value--;
 
 int value = 0;
 static int instances;
;
int Struct1::instances = 0;
struct Struct2 : public Struct1
 explicit Struct2(const int aVal)
 : Struct1(aVal)
 
 
;
struct Struct3 : public Struct1
 Struct3(const Struct2 &other)
 : Struct1(other.value)
 
 
 Struct3(const Struct2 &&other)
 : Struct1(other.value)
 
 
 Struct3 operator=(const Struct2 &other)
 
 Struct3 ret(other);
 return ret;
 
 Struct3 operator=(Struct2 &&other)
 
 Struct3 ret(other);
 other.~Struct2();
 return ret;
 
;
struct unique_pointer_test
 static void run()
 
 empty_pointer_test();
 basic_pointer_test();
 reset_pointer_test();
 compare_pointer_test();
 swap_pointer_test();
 std_container_test();
 
 static void empty_pointer_test()
 
 // Create an empty (ie. nullptr) UniquePointer
 UniquePointer<Struct2> xPtr = nullptr;
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 // Reset to nullptr (ie. do nothing)
 xPtr.reset();
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 
 static void basic_pointer_test()
 
 
 // Create a UniquePointer
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 if (xPtr)
 
 EXPECT_EQ(123, xPtr->value);
 EXPECT_EQ(1, xPtr->instances);
 EXPECT_EQ(1, Struct1::instances);
 // call a function
 xPtr->incr();
 EXPECT_EQ(124, xPtr->value);
 (*xPtr).incr();
 EXPECT_EQ(125, (*xPtr).value);
 xPtr->decr();
 xPtr->decr();
 // Copy construct the UniquePointer, transferring ownership
 UniquePointer<Struct2> yPtr(std::move(xPtr));
 xPtr.reset();
 EXPECT_NE(xPtr, yPtr);
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_TRUE(yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(123, yPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 if (yPtr)
 
 UniquePointer<Struct2> zPtr = std::move(yPtr);
 yPtr.reset();
 EXPECT_NE(yPtr, zPtr);
 EXPECT_FALSE(yPtr);
 EXPECT_EQ(nullptr, yPtr.get());
 EXPECT_TRUE(zPtr);
 EXPECT_NE(nullptr, zPtr.get());
 EXPECT_EQ(123, zPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_FALSE(yPtr);
 EXPECT_EQ(nullptr, yPtr.get());
 EXPECT_EQ(0, Struct1::instances);
 
 else
 
 assert(false); //"bool cast operator error"
 
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_EQ(0, Struct1::instances);
 
 EXPECT_EQ(0, Struct1::instances);
 
 static void reset_pointer_test()
 
 // Create an empty (ie. nullptr) UniquePointer
 UniquePointer<Struct2> xPtr;
 // Reset it with a new pointer
 xPtr.reset(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123, xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 Struct2* pX = xPtr.get();
 // Reset it with another new pointer
 xPtr.reset(new Struct2(234));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(234, xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 EXPECT_NE(pX, xPtr.get());
 // Move-construct a new UniquePointer to the same object, transferring ownership
 UniquePointer<Struct2> yPtr = std::move(xPtr);
 xPtr.reset();
 EXPECT_NE(xPtr, yPtr);
 EXPECT_FALSE( xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_TRUE( yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(234, yPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 // Reset to nullptr
 yPtr.reset();
 EXPECT_EQ(nullptr, yPtr.get());
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_EQ(0, Struct1::instances);
 
 static void compare_pointer_test()
 
 // Create a UniquePointer
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123,xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 Struct2* pX = xPtr.get();
 // Create another UniquePointer
 UniquePointer<Struct2> yPtr(new Struct2(234));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123,xPtr->value);
 EXPECT_EQ(2, Struct1::instances);
 EXPECT_TRUE(yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(234, yPtr->value);
 Struct2* pY = yPtr.get();
 EXPECT_NE(xPtr, yPtr);
 EXPECT_NE(pY->value, pX->value);
 
 static void swap_pointer_test()
 
 // Create a UniquePointer
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123,xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 // Create another UniquePointer
 UniquePointer<Struct2> yPtr(new Struct2(234));
 EXPECT_TRUE(yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(234, yPtr->value);
 EXPECT_EQ(2, Struct1::instances);
 EXPECT_LT(xPtr->value, yPtr->value);
 xPtr.swap(yPtr);
 EXPECT_GT(xPtr->value, yPtr->value);
 EXPECT_TRUE(xPtr);
 EXPECT_TRUE(yPtr);
 
 static void std_container_test()
 
 // Create a shared_ptr
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123, xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 Struct2* pX = xPtr.get();
 
 std::vector<UniquePointer<Struct2> > PtrList;
 // Move-it inside a container, transferring ownership
 PtrList.push_back(std::move(xPtr));
 EXPECT_FALSE(xPtr);
 EXPECT_TRUE( PtrList.back());
 EXPECT_EQ(pX,PtrList.back().get());
 EXPECT_EQ(1, Struct1::instances);
 // Destructor of the vector releases the last pointer thus destroying the object
 EXPECT_EQ(0, Struct1::instances);
 
;
int main(int argc, char *argv)
 (void)(argc);
 (void)(argv);
 unique_pointer_test::run();
 return 0;
Edit: here's the code on Wandbox
Edit 2: Removed need for C++17. Compiles with C++14.
c++ c++14 pointers qt
add a comment |Â
up vote
11
down vote
favorite
I'm a hobby programmer, so I've never been through a code review before (online or offline). That said, here goes:
Background
I use Qt extensively but have long wanted QScopedPointer to be a moveable type. Since the Qt devs don't plan to implement this, I started trying to implement it myself. This was several years ago. [Pause for laughter]
Purpose
My goal is to not simply add move semantics to QScopedPointer:
- To maintain as much compatibility with the Standard Library as reasonably possible by- adding a partial specialization of std::hashandstd::swap,
- declaring the expected type traits,
- and implementing all of the public member functions in std::unique_pointer.
 
- adding a partial specialization of 
- To also maintain compatibility with Qt, so that qe::UniquePointeracts as a drop-in replacement forQScopedPointer.
- Use C++11 and later where appropriate (e.g., explicit operator bool). Currently, it compiles with C++14 as a minimum.
Notes
I kept the Qt-style deleter to allow use of existing deleters for QScopedPointer. The lambdas and macros in test.h are my quick-and-dirty GTest stand-in.
Goals
- I want to make sure the fundamentals are sound. Despite testing, experienced eyeballs are good.
- I don't use Boost and don't have a lot of Standard Library experience. Did I miss any usingdeclarations? Is my partial specialization ofstd::hashcorrectly done?
- I'm least confident about the conversion functions (accepting qe::UniquePointer<U, Cleanup>). Have I made any obvious mistakes that may create edge cases?
- Of course, I may have missed something for which the test class simply does not test. Any insight there would be appreciated.
Code
uniquepointer.h
#ifndef QE_CORE_UNIQUEPOINTER_H
#define QE_CORE_UNIQUEPOINTER_H
#include <type_traits>
namespace qe 
namespace detail 
template <class T>
constexpr void assertCompleteType() noexcept
 using forced_complete_type = char[sizeof(T) ? 1 : -1];
 (void)sizeof(forced_complete_type);
template <class T, class U>
void assertConvertible()
 using convertible = char[sizeof(std::is_convertible<T, U>()) ? 1 : -1];
 (void)sizeof(convertible);
 // namespace detail
template <typename T>
struct DefaultDeleter
 static inline void cleanup(T *pointer)
 
 if (pointer) 
 qe::detail::assertCompleteType<T>();
 delete pointer;
 
 
;
template <class T, class Cleanup = DefaultDeleter<T>>
class UniquePointer
public:
 using this_type = UniquePointer<T, Cleanup>;
 using element_type = T;
 using pointer = std::add_pointer_t<T>;
 using const_pointer = std::add_const_t<pointer>;
 using reference = std::add_lvalue_reference_t<T>;
 using const_reference = std::add_const_t<reference>;
 using deleter_type = Cleanup;
 inline UniquePointer(pointer p = nullptr) noexcept : d(p) 
 inline UniquePointer(this_type && other) noexcept : d(other.take()) 
 template <class U>
 inline UniquePointer(UniquePointer<U, Cleanup> && other) noexcept(std::is_convertible<U, T>())
 : d(other.take())
 
 qe::detail::assertConvertible<U, T>();
 
 inline this_type &operator=(this_type &&other)
 
 if (*this != other)
 reset(other.take());
 return *this;
 
 template <class U>
 inline this_type &operator=(UniquePointer<U, Cleanup> &&other)
 noexcept(std::is_convertible<U, T>())
 
 qe::detail::assertConvertible<U, T>();
 if (*this != other)
 reset(other.take());
 return *this;
 
 UniquePointer(const this_type &) = delete;
 this_type &operator=(const this_type &) = delete;
 inline ~UniquePointer()
 
 reset(nullptr);
 
 pointer take() noexcept
 
 auto oldD = this->d;
 this->d = nullptr;
 return oldD;
 
 inline void reset(pointer other = nullptr)
 
 if (this->d == other)
 return;
 auto oldD = this->d;
 if (oldD)
 deleter_type::cleanup(oldD);
 this->d = other;
 
 inline pointer data() const noexcept return this->d; 
 inline pointer get() const noexcept return this->d; 
 inline pointer* addressOf() noexcept return &(this->d); 
 explicit operator bool() const noexcept return !isNull(); 
 inline bool operator!() const noexcept return isNull(); 
 inline reference operator*() const noexcept return *(this->d); 
 inline pointer operator->() const noexcept return this->d; 
 inline bool isNull() const noexcept return !(this->d); 
 inline void swap(UniquePointer &other) noexcept std::swap(this->d, other.d); 
 template <class U>
 void swap(UniquePointer<U, Cleanup> &other)
 noexcept(std::is_convertible<U, T>() && std::is_convertible<T, U>())
 
 qe::detail::assertConvertible<U, T>();
 qe::detail::assertConvertible<T, U>();
 std::swap(this->d, other.d);
 
 pointer release() noexcept return take(); 
private:
 pointer d;
;
template <class T, class... Args>
inline UniquePointer<T> makeUnique(Args && ...args)
 return UniquePointer<T>(new T(std::forward<Args>(args)...));
 //namespace qe
template <class T, class Cleanup>
inline bool operator==(const qe::UniquePointer<T, Cleanup> &lhs, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return lhs.data() == rhs.data();
template<class T, class Cleanup>
inline bool operator ==(const qe::UniquePointer<T, Cleanup> &lhs, std::nullptr_t) noexcept
 return lhs.isNull();
template<class T, class Cleanup>
inline bool operator ==(std::nullptr_t, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return rhs.isNull();
template <class T, class Cleanup>
inline bool operator!=(const qe::UniquePointer<T, Cleanup> &lhs, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return lhs.data() != rhs.data();
template <class T, class Cleanup>
inline bool operator !=(const qe::UniquePointer<T, Cleanup> &lhs, std::nullptr_t) noexcept
 return !lhs.data();
template <class T, class Cleanup>
inline bool operator !=(std::nullptr_t, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return !rhs.data();
namespace std 
template <class T, class Cleanup>
inline void swap(qe::UniquePointer<T, Cleanup> &lhs, qe::UniquePointer<T, Cleanup> &rhs) noexcept
 lhs.swap(rhs);
template <class T, class Cleanup>
struct hash<qe::UniquePointer<T, Cleanup>>
 using argument_type = qe::UniquePointer<T, Cleanup>;
 using result_type = std::size_t;
 inline result_type operator()(const argument_type & p) const noexcept
 
 return std::hash<T *>(p.data());
 
;
 //namespace std
#endif //QE_CORE_UNIQUEPOINTER_H
test.h
#ifndef QE_TEST_TEST_H
#define QE_TEST_TEST_H
#include <assert.h>
auto equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs == rhs) && (rhs == lhs);
;
auto not_equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs != rhs);
;
auto less_than_check = (auto && lhs, auto && rhs) -> bool
 return (lhs < rhs);
;
auto less_than_or_equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs <= rhs);
;
auto greater_than_check = (auto && lhs, auto && rhs) -> bool
 return (lhs > rhs);
;
auto greater_than_or_equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs >= rhs);
;
auto implicit_true_check = (auto && lhs) -> bool
 return lhs ? true : false;
;
auto implicit_false_check = (auto && lhs) -> bool
 return lhs ? false : true;
;
#define EXPECT_EQ(x, y) assert(equal_check(x, y))
#define EXPECT_NE(x, y) assert(not_equal_check(x, y))
#define EXPECT_LT(x, y) assert(less_than_check(x, y))
#define EXPECT_LE(x, y) assert(less_than_or_equal_check(x, y))
#define EXPECT_GT(x, y) assert(greater_than_check(x, y))
#define EXPECT_GE(x, y) assert(greater_than_or_equal_check(x, y))
#define EXPECT_TRUE(x) assert(implicit_true_check(x))
#define EXPECT_FALSE(x) assert(implicit_false_check(x))
#endif // QE_TEST_TEST_H
main.cpp
#include <vector>
#include "uniquepointer.h"
#include "test.h"
using namespace qe;
struct Struct1
 explicit Struct1(int aVal)
 : value(aVal)
 
 instances++;
 
 Struct1(const Struct1 &) = default;
 Struct1(Struct1 &&) = default;
 ~Struct1()
 
 instances--;
 
 Struct1 &operator =(const Struct1 &) = default;
 Struct1 &operator =(Struct1 &&) = default;
 void incr()
 
 value++;
 
 void decr()
 
 value--;
 
 int value = 0;
 static int instances;
;
int Struct1::instances = 0;
struct Struct2 : public Struct1
 explicit Struct2(const int aVal)
 : Struct1(aVal)
 
 
;
struct Struct3 : public Struct1
 Struct3(const Struct2 &other)
 : Struct1(other.value)
 
 
 Struct3(const Struct2 &&other)
 : Struct1(other.value)
 
 
 Struct3 operator=(const Struct2 &other)
 
 Struct3 ret(other);
 return ret;
 
 Struct3 operator=(Struct2 &&other)
 
 Struct3 ret(other);
 other.~Struct2();
 return ret;
 
;
struct unique_pointer_test
 static void run()
 
 empty_pointer_test();
 basic_pointer_test();
 reset_pointer_test();
 compare_pointer_test();
 swap_pointer_test();
 std_container_test();
 
 static void empty_pointer_test()
 
 // Create an empty (ie. nullptr) UniquePointer
 UniquePointer<Struct2> xPtr = nullptr;
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 // Reset to nullptr (ie. do nothing)
 xPtr.reset();
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 
 static void basic_pointer_test()
 
 
 // Create a UniquePointer
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 if (xPtr)
 
 EXPECT_EQ(123, xPtr->value);
 EXPECT_EQ(1, xPtr->instances);
 EXPECT_EQ(1, Struct1::instances);
 // call a function
 xPtr->incr();
 EXPECT_EQ(124, xPtr->value);
 (*xPtr).incr();
 EXPECT_EQ(125, (*xPtr).value);
 xPtr->decr();
 xPtr->decr();
 // Copy construct the UniquePointer, transferring ownership
 UniquePointer<Struct2> yPtr(std::move(xPtr));
 xPtr.reset();
 EXPECT_NE(xPtr, yPtr);
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_TRUE(yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(123, yPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 if (yPtr)
 
 UniquePointer<Struct2> zPtr = std::move(yPtr);
 yPtr.reset();
 EXPECT_NE(yPtr, zPtr);
 EXPECT_FALSE(yPtr);
 EXPECT_EQ(nullptr, yPtr.get());
 EXPECT_TRUE(zPtr);
 EXPECT_NE(nullptr, zPtr.get());
 EXPECT_EQ(123, zPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_FALSE(yPtr);
 EXPECT_EQ(nullptr, yPtr.get());
 EXPECT_EQ(0, Struct1::instances);
 
 else
 
 assert(false); //"bool cast operator error"
 
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_EQ(0, Struct1::instances);
 
 EXPECT_EQ(0, Struct1::instances);
 
 static void reset_pointer_test()
 
 // Create an empty (ie. nullptr) UniquePointer
 UniquePointer<Struct2> xPtr;
 // Reset it with a new pointer
 xPtr.reset(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123, xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 Struct2* pX = xPtr.get();
 // Reset it with another new pointer
 xPtr.reset(new Struct2(234));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(234, xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 EXPECT_NE(pX, xPtr.get());
 // Move-construct a new UniquePointer to the same object, transferring ownership
 UniquePointer<Struct2> yPtr = std::move(xPtr);
 xPtr.reset();
 EXPECT_NE(xPtr, yPtr);
 EXPECT_FALSE( xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_TRUE( yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(234, yPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 // Reset to nullptr
 yPtr.reset();
 EXPECT_EQ(nullptr, yPtr.get());
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_EQ(0, Struct1::instances);
 
 static void compare_pointer_test()
 
 // Create a UniquePointer
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123,xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 Struct2* pX = xPtr.get();
 // Create another UniquePointer
 UniquePointer<Struct2> yPtr(new Struct2(234));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123,xPtr->value);
 EXPECT_EQ(2, Struct1::instances);
 EXPECT_TRUE(yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(234, yPtr->value);
 Struct2* pY = yPtr.get();
 EXPECT_NE(xPtr, yPtr);
 EXPECT_NE(pY->value, pX->value);
 
 static void swap_pointer_test()
 
 // Create a UniquePointer
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123,xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 // Create another UniquePointer
 UniquePointer<Struct2> yPtr(new Struct2(234));
 EXPECT_TRUE(yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(234, yPtr->value);
 EXPECT_EQ(2, Struct1::instances);
 EXPECT_LT(xPtr->value, yPtr->value);
 xPtr.swap(yPtr);
 EXPECT_GT(xPtr->value, yPtr->value);
 EXPECT_TRUE(xPtr);
 EXPECT_TRUE(yPtr);
 
 static void std_container_test()
 
 // Create a shared_ptr
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123, xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 Struct2* pX = xPtr.get();
 
 std::vector<UniquePointer<Struct2> > PtrList;
 // Move-it inside a container, transferring ownership
 PtrList.push_back(std::move(xPtr));
 EXPECT_FALSE(xPtr);
 EXPECT_TRUE( PtrList.back());
 EXPECT_EQ(pX,PtrList.back().get());
 EXPECT_EQ(1, Struct1::instances);
 // Destructor of the vector releases the last pointer thus destroying the object
 EXPECT_EQ(0, Struct1::instances);
 
;
int main(int argc, char *argv)
 (void)(argc);
 (void)(argv);
 unique_pointer_test::run();
 return 0;
Edit: here's the code on Wandbox
Edit 2: Removed need for C++17. Compiles with C++14.
c++ c++14 pointers qt
 
 
 2
 
 
 
 
 Welcome to Code Review, and congratulations on writing a well-written question on your first go.
 â Mast
 May 20 at 22:59
 
 
 
add a comment |Â
up vote
11
down vote
favorite
up vote
11
down vote
favorite
I'm a hobby programmer, so I've never been through a code review before (online or offline). That said, here goes:
Background
I use Qt extensively but have long wanted QScopedPointer to be a moveable type. Since the Qt devs don't plan to implement this, I started trying to implement it myself. This was several years ago. [Pause for laughter]
Purpose
My goal is to not simply add move semantics to QScopedPointer:
- To maintain as much compatibility with the Standard Library as reasonably possible by- adding a partial specialization of std::hashandstd::swap,
- declaring the expected type traits,
- and implementing all of the public member functions in std::unique_pointer.
 
- adding a partial specialization of 
- To also maintain compatibility with Qt, so that qe::UniquePointeracts as a drop-in replacement forQScopedPointer.
- Use C++11 and later where appropriate (e.g., explicit operator bool). Currently, it compiles with C++14 as a minimum.
Notes
I kept the Qt-style deleter to allow use of existing deleters for QScopedPointer. The lambdas and macros in test.h are my quick-and-dirty GTest stand-in.
Goals
- I want to make sure the fundamentals are sound. Despite testing, experienced eyeballs are good.
- I don't use Boost and don't have a lot of Standard Library experience. Did I miss any usingdeclarations? Is my partial specialization ofstd::hashcorrectly done?
- I'm least confident about the conversion functions (accepting qe::UniquePointer<U, Cleanup>). Have I made any obvious mistakes that may create edge cases?
- Of course, I may have missed something for which the test class simply does not test. Any insight there would be appreciated.
Code
uniquepointer.h
#ifndef QE_CORE_UNIQUEPOINTER_H
#define QE_CORE_UNIQUEPOINTER_H
#include <type_traits>
namespace qe 
namespace detail 
template <class T>
constexpr void assertCompleteType() noexcept
 using forced_complete_type = char[sizeof(T) ? 1 : -1];
 (void)sizeof(forced_complete_type);
template <class T, class U>
void assertConvertible()
 using convertible = char[sizeof(std::is_convertible<T, U>()) ? 1 : -1];
 (void)sizeof(convertible);
 // namespace detail
template <typename T>
struct DefaultDeleter
 static inline void cleanup(T *pointer)
 
 if (pointer) 
 qe::detail::assertCompleteType<T>();
 delete pointer;
 
 
;
template <class T, class Cleanup = DefaultDeleter<T>>
class UniquePointer
public:
 using this_type = UniquePointer<T, Cleanup>;
 using element_type = T;
 using pointer = std::add_pointer_t<T>;
 using const_pointer = std::add_const_t<pointer>;
 using reference = std::add_lvalue_reference_t<T>;
 using const_reference = std::add_const_t<reference>;
 using deleter_type = Cleanup;
 inline UniquePointer(pointer p = nullptr) noexcept : d(p) 
 inline UniquePointer(this_type && other) noexcept : d(other.take()) 
 template <class U>
 inline UniquePointer(UniquePointer<U, Cleanup> && other) noexcept(std::is_convertible<U, T>())
 : d(other.take())
 
 qe::detail::assertConvertible<U, T>();
 
 inline this_type &operator=(this_type &&other)
 
 if (*this != other)
 reset(other.take());
 return *this;
 
 template <class U>
 inline this_type &operator=(UniquePointer<U, Cleanup> &&other)
 noexcept(std::is_convertible<U, T>())
 
 qe::detail::assertConvertible<U, T>();
 if (*this != other)
 reset(other.take());
 return *this;
 
 UniquePointer(const this_type &) = delete;
 this_type &operator=(const this_type &) = delete;
 inline ~UniquePointer()
 
 reset(nullptr);
 
 pointer take() noexcept
 
 auto oldD = this->d;
 this->d = nullptr;
 return oldD;
 
 inline void reset(pointer other = nullptr)
 
 if (this->d == other)
 return;
 auto oldD = this->d;
 if (oldD)
 deleter_type::cleanup(oldD);
 this->d = other;
 
 inline pointer data() const noexcept return this->d; 
 inline pointer get() const noexcept return this->d; 
 inline pointer* addressOf() noexcept return &(this->d); 
 explicit operator bool() const noexcept return !isNull(); 
 inline bool operator!() const noexcept return isNull(); 
 inline reference operator*() const noexcept return *(this->d); 
 inline pointer operator->() const noexcept return this->d; 
 inline bool isNull() const noexcept return !(this->d); 
 inline void swap(UniquePointer &other) noexcept std::swap(this->d, other.d); 
 template <class U>
 void swap(UniquePointer<U, Cleanup> &other)
 noexcept(std::is_convertible<U, T>() && std::is_convertible<T, U>())
 
 qe::detail::assertConvertible<U, T>();
 qe::detail::assertConvertible<T, U>();
 std::swap(this->d, other.d);
 
 pointer release() noexcept return take(); 
private:
 pointer d;
;
template <class T, class... Args>
inline UniquePointer<T> makeUnique(Args && ...args)
 return UniquePointer<T>(new T(std::forward<Args>(args)...));
 //namespace qe
template <class T, class Cleanup>
inline bool operator==(const qe::UniquePointer<T, Cleanup> &lhs, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return lhs.data() == rhs.data();
template<class T, class Cleanup>
inline bool operator ==(const qe::UniquePointer<T, Cleanup> &lhs, std::nullptr_t) noexcept
 return lhs.isNull();
template<class T, class Cleanup>
inline bool operator ==(std::nullptr_t, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return rhs.isNull();
template <class T, class Cleanup>
inline bool operator!=(const qe::UniquePointer<T, Cleanup> &lhs, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return lhs.data() != rhs.data();
template <class T, class Cleanup>
inline bool operator !=(const qe::UniquePointer<T, Cleanup> &lhs, std::nullptr_t) noexcept
 return !lhs.data();
template <class T, class Cleanup>
inline bool operator !=(std::nullptr_t, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return !rhs.data();
namespace std 
template <class T, class Cleanup>
inline void swap(qe::UniquePointer<T, Cleanup> &lhs, qe::UniquePointer<T, Cleanup> &rhs) noexcept
 lhs.swap(rhs);
template <class T, class Cleanup>
struct hash<qe::UniquePointer<T, Cleanup>>
 using argument_type = qe::UniquePointer<T, Cleanup>;
 using result_type = std::size_t;
 inline result_type operator()(const argument_type & p) const noexcept
 
 return std::hash<T *>(p.data());
 
;
 //namespace std
#endif //QE_CORE_UNIQUEPOINTER_H
test.h
#ifndef QE_TEST_TEST_H
#define QE_TEST_TEST_H
#include <assert.h>
auto equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs == rhs) && (rhs == lhs);
;
auto not_equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs != rhs);
;
auto less_than_check = (auto && lhs, auto && rhs) -> bool
 return (lhs < rhs);
;
auto less_than_or_equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs <= rhs);
;
auto greater_than_check = (auto && lhs, auto && rhs) -> bool
 return (lhs > rhs);
;
auto greater_than_or_equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs >= rhs);
;
auto implicit_true_check = (auto && lhs) -> bool
 return lhs ? true : false;
;
auto implicit_false_check = (auto && lhs) -> bool
 return lhs ? false : true;
;
#define EXPECT_EQ(x, y) assert(equal_check(x, y))
#define EXPECT_NE(x, y) assert(not_equal_check(x, y))
#define EXPECT_LT(x, y) assert(less_than_check(x, y))
#define EXPECT_LE(x, y) assert(less_than_or_equal_check(x, y))
#define EXPECT_GT(x, y) assert(greater_than_check(x, y))
#define EXPECT_GE(x, y) assert(greater_than_or_equal_check(x, y))
#define EXPECT_TRUE(x) assert(implicit_true_check(x))
#define EXPECT_FALSE(x) assert(implicit_false_check(x))
#endif // QE_TEST_TEST_H
main.cpp
#include <vector>
#include "uniquepointer.h"
#include "test.h"
using namespace qe;
struct Struct1
 explicit Struct1(int aVal)
 : value(aVal)
 
 instances++;
 
 Struct1(const Struct1 &) = default;
 Struct1(Struct1 &&) = default;
 ~Struct1()
 
 instances--;
 
 Struct1 &operator =(const Struct1 &) = default;
 Struct1 &operator =(Struct1 &&) = default;
 void incr()
 
 value++;
 
 void decr()
 
 value--;
 
 int value = 0;
 static int instances;
;
int Struct1::instances = 0;
struct Struct2 : public Struct1
 explicit Struct2(const int aVal)
 : Struct1(aVal)
 
 
;
struct Struct3 : public Struct1
 Struct3(const Struct2 &other)
 : Struct1(other.value)
 
 
 Struct3(const Struct2 &&other)
 : Struct1(other.value)
 
 
 Struct3 operator=(const Struct2 &other)
 
 Struct3 ret(other);
 return ret;
 
 Struct3 operator=(Struct2 &&other)
 
 Struct3 ret(other);
 other.~Struct2();
 return ret;
 
;
struct unique_pointer_test
 static void run()
 
 empty_pointer_test();
 basic_pointer_test();
 reset_pointer_test();
 compare_pointer_test();
 swap_pointer_test();
 std_container_test();
 
 static void empty_pointer_test()
 
 // Create an empty (ie. nullptr) UniquePointer
 UniquePointer<Struct2> xPtr = nullptr;
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 // Reset to nullptr (ie. do nothing)
 xPtr.reset();
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 
 static void basic_pointer_test()
 
 
 // Create a UniquePointer
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 if (xPtr)
 
 EXPECT_EQ(123, xPtr->value);
 EXPECT_EQ(1, xPtr->instances);
 EXPECT_EQ(1, Struct1::instances);
 // call a function
 xPtr->incr();
 EXPECT_EQ(124, xPtr->value);
 (*xPtr).incr();
 EXPECT_EQ(125, (*xPtr).value);
 xPtr->decr();
 xPtr->decr();
 // Copy construct the UniquePointer, transferring ownership
 UniquePointer<Struct2> yPtr(std::move(xPtr));
 xPtr.reset();
 EXPECT_NE(xPtr, yPtr);
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_TRUE(yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(123, yPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 if (yPtr)
 
 UniquePointer<Struct2> zPtr = std::move(yPtr);
 yPtr.reset();
 EXPECT_NE(yPtr, zPtr);
 EXPECT_FALSE(yPtr);
 EXPECT_EQ(nullptr, yPtr.get());
 EXPECT_TRUE(zPtr);
 EXPECT_NE(nullptr, zPtr.get());
 EXPECT_EQ(123, zPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_FALSE(yPtr);
 EXPECT_EQ(nullptr, yPtr.get());
 EXPECT_EQ(0, Struct1::instances);
 
 else
 
 assert(false); //"bool cast operator error"
 
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_EQ(0, Struct1::instances);
 
 EXPECT_EQ(0, Struct1::instances);
 
 static void reset_pointer_test()
 
 // Create an empty (ie. nullptr) UniquePointer
 UniquePointer<Struct2> xPtr;
 // Reset it with a new pointer
 xPtr.reset(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123, xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 Struct2* pX = xPtr.get();
 // Reset it with another new pointer
 xPtr.reset(new Struct2(234));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(234, xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 EXPECT_NE(pX, xPtr.get());
 // Move-construct a new UniquePointer to the same object, transferring ownership
 UniquePointer<Struct2> yPtr = std::move(xPtr);
 xPtr.reset();
 EXPECT_NE(xPtr, yPtr);
 EXPECT_FALSE( xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_TRUE( yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(234, yPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 // Reset to nullptr
 yPtr.reset();
 EXPECT_EQ(nullptr, yPtr.get());
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_EQ(0, Struct1::instances);
 
 static void compare_pointer_test()
 
 // Create a UniquePointer
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123,xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 Struct2* pX = xPtr.get();
 // Create another UniquePointer
 UniquePointer<Struct2> yPtr(new Struct2(234));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123,xPtr->value);
 EXPECT_EQ(2, Struct1::instances);
 EXPECT_TRUE(yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(234, yPtr->value);
 Struct2* pY = yPtr.get();
 EXPECT_NE(xPtr, yPtr);
 EXPECT_NE(pY->value, pX->value);
 
 static void swap_pointer_test()
 
 // Create a UniquePointer
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123,xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 // Create another UniquePointer
 UniquePointer<Struct2> yPtr(new Struct2(234));
 EXPECT_TRUE(yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(234, yPtr->value);
 EXPECT_EQ(2, Struct1::instances);
 EXPECT_LT(xPtr->value, yPtr->value);
 xPtr.swap(yPtr);
 EXPECT_GT(xPtr->value, yPtr->value);
 EXPECT_TRUE(xPtr);
 EXPECT_TRUE(yPtr);
 
 static void std_container_test()
 
 // Create a shared_ptr
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123, xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 Struct2* pX = xPtr.get();
 
 std::vector<UniquePointer<Struct2> > PtrList;
 // Move-it inside a container, transferring ownership
 PtrList.push_back(std::move(xPtr));
 EXPECT_FALSE(xPtr);
 EXPECT_TRUE( PtrList.back());
 EXPECT_EQ(pX,PtrList.back().get());
 EXPECT_EQ(1, Struct1::instances);
 // Destructor of the vector releases the last pointer thus destroying the object
 EXPECT_EQ(0, Struct1::instances);
 
;
int main(int argc, char *argv)
 (void)(argc);
 (void)(argv);
 unique_pointer_test::run();
 return 0;
Edit: here's the code on Wandbox
Edit 2: Removed need for C++17. Compiles with C++14.
c++ c++14 pointers qt
I'm a hobby programmer, so I've never been through a code review before (online or offline). That said, here goes:
Background
I use Qt extensively but have long wanted QScopedPointer to be a moveable type. Since the Qt devs don't plan to implement this, I started trying to implement it myself. This was several years ago. [Pause for laughter]
Purpose
My goal is to not simply add move semantics to QScopedPointer:
- To maintain as much compatibility with the Standard Library as reasonably possible by- adding a partial specialization of std::hashandstd::swap,
- declaring the expected type traits,
- and implementing all of the public member functions in std::unique_pointer.
 
- adding a partial specialization of 
- To also maintain compatibility with Qt, so that qe::UniquePointeracts as a drop-in replacement forQScopedPointer.
- Use C++11 and later where appropriate (e.g., explicit operator bool). Currently, it compiles with C++14 as a minimum.
Notes
I kept the Qt-style deleter to allow use of existing deleters for QScopedPointer. The lambdas and macros in test.h are my quick-and-dirty GTest stand-in.
Goals
- I want to make sure the fundamentals are sound. Despite testing, experienced eyeballs are good.
- I don't use Boost and don't have a lot of Standard Library experience. Did I miss any usingdeclarations? Is my partial specialization ofstd::hashcorrectly done?
- I'm least confident about the conversion functions (accepting qe::UniquePointer<U, Cleanup>). Have I made any obvious mistakes that may create edge cases?
- Of course, I may have missed something for which the test class simply does not test. Any insight there would be appreciated.
Code
uniquepointer.h
#ifndef QE_CORE_UNIQUEPOINTER_H
#define QE_CORE_UNIQUEPOINTER_H
#include <type_traits>
namespace qe 
namespace detail 
template <class T>
constexpr void assertCompleteType() noexcept
 using forced_complete_type = char[sizeof(T) ? 1 : -1];
 (void)sizeof(forced_complete_type);
template <class T, class U>
void assertConvertible()
 using convertible = char[sizeof(std::is_convertible<T, U>()) ? 1 : -1];
 (void)sizeof(convertible);
 // namespace detail
template <typename T>
struct DefaultDeleter
 static inline void cleanup(T *pointer)
 
 if (pointer) 
 qe::detail::assertCompleteType<T>();
 delete pointer;
 
 
;
template <class T, class Cleanup = DefaultDeleter<T>>
class UniquePointer
public:
 using this_type = UniquePointer<T, Cleanup>;
 using element_type = T;
 using pointer = std::add_pointer_t<T>;
 using const_pointer = std::add_const_t<pointer>;
 using reference = std::add_lvalue_reference_t<T>;
 using const_reference = std::add_const_t<reference>;
 using deleter_type = Cleanup;
 inline UniquePointer(pointer p = nullptr) noexcept : d(p) 
 inline UniquePointer(this_type && other) noexcept : d(other.take()) 
 template <class U>
 inline UniquePointer(UniquePointer<U, Cleanup> && other) noexcept(std::is_convertible<U, T>())
 : d(other.take())
 
 qe::detail::assertConvertible<U, T>();
 
 inline this_type &operator=(this_type &&other)
 
 if (*this != other)
 reset(other.take());
 return *this;
 
 template <class U>
 inline this_type &operator=(UniquePointer<U, Cleanup> &&other)
 noexcept(std::is_convertible<U, T>())
 
 qe::detail::assertConvertible<U, T>();
 if (*this != other)
 reset(other.take());
 return *this;
 
 UniquePointer(const this_type &) = delete;
 this_type &operator=(const this_type &) = delete;
 inline ~UniquePointer()
 
 reset(nullptr);
 
 pointer take() noexcept
 
 auto oldD = this->d;
 this->d = nullptr;
 return oldD;
 
 inline void reset(pointer other = nullptr)
 
 if (this->d == other)
 return;
 auto oldD = this->d;
 if (oldD)
 deleter_type::cleanup(oldD);
 this->d = other;
 
 inline pointer data() const noexcept return this->d; 
 inline pointer get() const noexcept return this->d; 
 inline pointer* addressOf() noexcept return &(this->d); 
 explicit operator bool() const noexcept return !isNull(); 
 inline bool operator!() const noexcept return isNull(); 
 inline reference operator*() const noexcept return *(this->d); 
 inline pointer operator->() const noexcept return this->d; 
 inline bool isNull() const noexcept return !(this->d); 
 inline void swap(UniquePointer &other) noexcept std::swap(this->d, other.d); 
 template <class U>
 void swap(UniquePointer<U, Cleanup> &other)
 noexcept(std::is_convertible<U, T>() && std::is_convertible<T, U>())
 
 qe::detail::assertConvertible<U, T>();
 qe::detail::assertConvertible<T, U>();
 std::swap(this->d, other.d);
 
 pointer release() noexcept return take(); 
private:
 pointer d;
;
template <class T, class... Args>
inline UniquePointer<T> makeUnique(Args && ...args)
 return UniquePointer<T>(new T(std::forward<Args>(args)...));
 //namespace qe
template <class T, class Cleanup>
inline bool operator==(const qe::UniquePointer<T, Cleanup> &lhs, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return lhs.data() == rhs.data();
template<class T, class Cleanup>
inline bool operator ==(const qe::UniquePointer<T, Cleanup> &lhs, std::nullptr_t) noexcept
 return lhs.isNull();
template<class T, class Cleanup>
inline bool operator ==(std::nullptr_t, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return rhs.isNull();
template <class T, class Cleanup>
inline bool operator!=(const qe::UniquePointer<T, Cleanup> &lhs, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return lhs.data() != rhs.data();
template <class T, class Cleanup>
inline bool operator !=(const qe::UniquePointer<T, Cleanup> &lhs, std::nullptr_t) noexcept
 return !lhs.data();
template <class T, class Cleanup>
inline bool operator !=(std::nullptr_t, const qe::UniquePointer<T, Cleanup> &rhs) noexcept
 return !rhs.data();
namespace std 
template <class T, class Cleanup>
inline void swap(qe::UniquePointer<T, Cleanup> &lhs, qe::UniquePointer<T, Cleanup> &rhs) noexcept
 lhs.swap(rhs);
template <class T, class Cleanup>
struct hash<qe::UniquePointer<T, Cleanup>>
 using argument_type = qe::UniquePointer<T, Cleanup>;
 using result_type = std::size_t;
 inline result_type operator()(const argument_type & p) const noexcept
 
 return std::hash<T *>(p.data());
 
;
 //namespace std
#endif //QE_CORE_UNIQUEPOINTER_H
test.h
#ifndef QE_TEST_TEST_H
#define QE_TEST_TEST_H
#include <assert.h>
auto equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs == rhs) && (rhs == lhs);
;
auto not_equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs != rhs);
;
auto less_than_check = (auto && lhs, auto && rhs) -> bool
 return (lhs < rhs);
;
auto less_than_or_equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs <= rhs);
;
auto greater_than_check = (auto && lhs, auto && rhs) -> bool
 return (lhs > rhs);
;
auto greater_than_or_equal_check = (auto && lhs, auto && rhs) -> bool
 return (lhs >= rhs);
;
auto implicit_true_check = (auto && lhs) -> bool
 return lhs ? true : false;
;
auto implicit_false_check = (auto && lhs) -> bool
 return lhs ? false : true;
;
#define EXPECT_EQ(x, y) assert(equal_check(x, y))
#define EXPECT_NE(x, y) assert(not_equal_check(x, y))
#define EXPECT_LT(x, y) assert(less_than_check(x, y))
#define EXPECT_LE(x, y) assert(less_than_or_equal_check(x, y))
#define EXPECT_GT(x, y) assert(greater_than_check(x, y))
#define EXPECT_GE(x, y) assert(greater_than_or_equal_check(x, y))
#define EXPECT_TRUE(x) assert(implicit_true_check(x))
#define EXPECT_FALSE(x) assert(implicit_false_check(x))
#endif // QE_TEST_TEST_H
main.cpp
#include <vector>
#include "uniquepointer.h"
#include "test.h"
using namespace qe;
struct Struct1
 explicit Struct1(int aVal)
 : value(aVal)
 
 instances++;
 
 Struct1(const Struct1 &) = default;
 Struct1(Struct1 &&) = default;
 ~Struct1()
 
 instances--;
 
 Struct1 &operator =(const Struct1 &) = default;
 Struct1 &operator =(Struct1 &&) = default;
 void incr()
 
 value++;
 
 void decr()
 
 value--;
 
 int value = 0;
 static int instances;
;
int Struct1::instances = 0;
struct Struct2 : public Struct1
 explicit Struct2(const int aVal)
 : Struct1(aVal)
 
 
;
struct Struct3 : public Struct1
 Struct3(const Struct2 &other)
 : Struct1(other.value)
 
 
 Struct3(const Struct2 &&other)
 : Struct1(other.value)
 
 
 Struct3 operator=(const Struct2 &other)
 
 Struct3 ret(other);
 return ret;
 
 Struct3 operator=(Struct2 &&other)
 
 Struct3 ret(other);
 other.~Struct2();
 return ret;
 
;
struct unique_pointer_test
 static void run()
 
 empty_pointer_test();
 basic_pointer_test();
 reset_pointer_test();
 compare_pointer_test();
 swap_pointer_test();
 std_container_test();
 
 static void empty_pointer_test()
 
 // Create an empty (ie. nullptr) UniquePointer
 UniquePointer<Struct2> xPtr = nullptr;
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 // Reset to nullptr (ie. do nothing)
 xPtr.reset();
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 
 static void basic_pointer_test()
 
 
 // Create a UniquePointer
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 if (xPtr)
 
 EXPECT_EQ(123, xPtr->value);
 EXPECT_EQ(1, xPtr->instances);
 EXPECT_EQ(1, Struct1::instances);
 // call a function
 xPtr->incr();
 EXPECT_EQ(124, xPtr->value);
 (*xPtr).incr();
 EXPECT_EQ(125, (*xPtr).value);
 xPtr->decr();
 xPtr->decr();
 // Copy construct the UniquePointer, transferring ownership
 UniquePointer<Struct2> yPtr(std::move(xPtr));
 xPtr.reset();
 EXPECT_NE(xPtr, yPtr);
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_TRUE(yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(123, yPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 if (yPtr)
 
 UniquePointer<Struct2> zPtr = std::move(yPtr);
 yPtr.reset();
 EXPECT_NE(yPtr, zPtr);
 EXPECT_FALSE(yPtr);
 EXPECT_EQ(nullptr, yPtr.get());
 EXPECT_TRUE(zPtr);
 EXPECT_NE(nullptr, zPtr.get());
 EXPECT_EQ(123, zPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_FALSE(yPtr);
 EXPECT_EQ(nullptr, yPtr.get());
 EXPECT_EQ(0, Struct1::instances);
 
 else
 
 assert(false); //"bool cast operator error"
 
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_EQ(0, Struct1::instances);
 
 EXPECT_EQ(0, Struct1::instances);
 
 static void reset_pointer_test()
 
 // Create an empty (ie. nullptr) UniquePointer
 UniquePointer<Struct2> xPtr;
 // Reset it with a new pointer
 xPtr.reset(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123, xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 Struct2* pX = xPtr.get();
 // Reset it with another new pointer
 xPtr.reset(new Struct2(234));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(234, xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 EXPECT_NE(pX, xPtr.get());
 // Move-construct a new UniquePointer to the same object, transferring ownership
 UniquePointer<Struct2> yPtr = std::move(xPtr);
 xPtr.reset();
 EXPECT_NE(xPtr, yPtr);
 EXPECT_FALSE( xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_TRUE( yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(234, yPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 // Reset to nullptr
 yPtr.reset();
 EXPECT_EQ(nullptr, yPtr.get());
 EXPECT_FALSE(xPtr);
 EXPECT_EQ(nullptr, xPtr.get());
 EXPECT_EQ(0, Struct1::instances);
 
 static void compare_pointer_test()
 
 // Create a UniquePointer
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123,xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 Struct2* pX = xPtr.get();
 // Create another UniquePointer
 UniquePointer<Struct2> yPtr(new Struct2(234));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123,xPtr->value);
 EXPECT_EQ(2, Struct1::instances);
 EXPECT_TRUE(yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(234, yPtr->value);
 Struct2* pY = yPtr.get();
 EXPECT_NE(xPtr, yPtr);
 EXPECT_NE(pY->value, pX->value);
 
 static void swap_pointer_test()
 
 // Create a UniquePointer
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123,xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 // Create another UniquePointer
 UniquePointer<Struct2> yPtr(new Struct2(234));
 EXPECT_TRUE(yPtr);
 EXPECT_NE(nullptr, yPtr.get());
 EXPECT_EQ(234, yPtr->value);
 EXPECT_EQ(2, Struct1::instances);
 EXPECT_LT(xPtr->value, yPtr->value);
 xPtr.swap(yPtr);
 EXPECT_GT(xPtr->value, yPtr->value);
 EXPECT_TRUE(xPtr);
 EXPECT_TRUE(yPtr);
 
 static void std_container_test()
 
 // Create a shared_ptr
 UniquePointer<Struct2> xPtr(new Struct2(123));
 EXPECT_TRUE(xPtr);
 EXPECT_NE(nullptr, xPtr.get());
 EXPECT_EQ(123, xPtr->value);
 EXPECT_EQ(1, Struct1::instances);
 Struct2* pX = xPtr.get();
 
 std::vector<UniquePointer<Struct2> > PtrList;
 // Move-it inside a container, transferring ownership
 PtrList.push_back(std::move(xPtr));
 EXPECT_FALSE(xPtr);
 EXPECT_TRUE( PtrList.back());
 EXPECT_EQ(pX,PtrList.back().get());
 EXPECT_EQ(1, Struct1::instances);
 // Destructor of the vector releases the last pointer thus destroying the object
 EXPECT_EQ(0, Struct1::instances);
 
;
int main(int argc, char *argv)
 (void)(argc);
 (void)(argv);
 unique_pointer_test::run();
 return 0;
Edit: here's the code on Wandbox
Edit 2: Removed need for C++17. Compiles with C++14.
c++ c++14 pointers qt
edited May 20 at 20:55
asked May 20 at 20:18


Jon Harper
1587
1587
 
 
 2
 
 
 
 
 Welcome to Code Review, and congratulations on writing a well-written question on your first go.
 â Mast
 May 20 at 22:59
 
 
 
add a comment |Â
 
 
 2
 
 
 
 
 Welcome to Code Review, and congratulations on writing a well-written question on your first go.
 â Mast
 May 20 at 22:59
 
 
 
2
2
Welcome to Code Review, and congratulations on writing a well-written question on your first go.
â Mast
May 20 at 22:59
Welcome to Code Review, and congratulations on writing a well-written question on your first go.
â Mast
May 20 at 22:59
add a comment |Â
 3 Answers
 3
 
active
oldest
votes
up vote
4
down vote
accepted
Well, how about a radical idea:
Just use std::unique_ptr, the only extra-capability yours has is .addressOf(). Yes, you might have to write replacements for QScopedPointerPodDeleter and QScopedPointerDeleteLater if you use either, but that isn't hard:
struct FreeDeleter 
 static void operator()(void* p) noexcept free(p); 
;
struct DeleteLater 
 template <class T>
 static void operator()(T* p) noexcept p->deleteLater(); 
;
Anyway, now let's criticise how you wrote your own one:
- static_assertis the tool of choice for causing compile-time-errors if something doesn't check out at compile-time. Learn to use and love it.
- You can safely - deleteor- freea null-pointer. It's defined to do nothing.
- Inside the class-definition and out-of-line member-function-definition-bodies, you can use the injected name - UniquePointerto refer to refer to the class. No need to mention the template-parameters unless you want some unrelated class from the same template.
- Decorating an in-class-definition with - inlineis absolutely pointless.
- I'm not sure why you put that expression into the noexcept-specification of the templated ctor. You should either use it for SFINAE, or a - static_assert, but this makes no sense at all. I prefer the first:- template <class U, typename std::enable_if<std::is_convertible<U, T>()>::type>
 UniquePointer(UniquePointer<U, Cleanup> && other) noexcept
 : d(other.take())
- I suggest doing minimal work when move-assigning: Simply swap. 
- You know, simply delegating to what you already have written would simplify your templated move-assignment-operator: - template <class U>
 auto operator=(UniquePointer<U, Cleanup>&& other) noexcept
 -> decltype((*this = UniquePointer(other))) // The extra-parens are critical
 return (*this = UniquePointer(other));
- Copy-ctor and copy-assignment-operator are already implicitly deleted. 
- I wonder why you don't take advantage of the default-argument to - .reset()in the dtor.
- There is something really disturbing going on when you try to make your smart-pointer responsible for what it already is responsible for. It might make sense to assert that doesn't happen, but are you really sure that should silently do nothing? 
- Also in - .reset(), I got to wondering whether the deleter is responsible for handling null-pointers, or your smart-pointer, or both? You seem to really like testing for null-pointers everywhere, anyway the compiler should be able to deduplicate the code just fine.
- Your templated - .swap()is utterly useless. Again, the expression which should be used for SFINAE is in the- noexcept-specification, and you force a compile-time-error later when the body gets compiled instead, unless- Uis- T. In that case though, the non-templated version claims precedence.
- Write - operator==and- operator!=differently, and they will be far more comprehensive and useful:- template <class T, class D, class U>
 constexpr auto operator==(const UniquePointer<T, D>& a, const U& b) noexcept
 -> decltype(b == a.d)
 return b == a.d;
 template <class T, class D, class U>
 constexpr auto operator==(const U& a, const UniquePointer<T, D>& b) noexcept
 -> std::enable_if_t<std::is_pointer<U>() || std::is_null_pointer<U>(), decltype(a == b.d)>
 return a == b.d;- That also covers comparing with raw pointers, possible down-casting, and smart-pointers following the same pattern. 
- You should not put your own - swap()in- ::std. Even though it probably works, it's not allowed. Either rely on ADL to pick it up from the same namespace as your type, or depend on- std::swap()(which should be good enough anyway).
 
 
 
 
 
 
 re #6: the generic- std::swapwill use the classâÂÂs move assignment and move constructor, so do it the other way around: implement assignment, and let the library auto-generate- swap.
 â JDà Âugosz
 May 22 at 0:54
 
 
 
 
 
 
 
 
 
 a note on obsessively checking for null: use- gsl::not_nulland the compiler only inserts a check once, where needed.
 â JDà Âugosz
 May 22 at 0:57
 
 
 
 
 
 
 
 
 
 Regarding 0: why I don't use- unique_ptr? Because this is a hobby for me. I'm learning the motivations by studying implementations and trying my own hand at it. I have no professional aspirations. =) Oh, and I also originally started this before I had access to a C++11 compiler.
 â Jon Harper
 May 22 at 1:20
 
 
 
 
 
 
 
 
 
 
 @JonHarper: He has- .swap()already. Also, he could swap the only member.
 â Deduplicator
 May 22 at 2:56
 
 
 
 
 
 
 
 
 
 Re #14: Partial specializations of- stdtemplates are allowed: "It is allowed to add template specializations for any standard library template to the namespace std only if the declaration depends on a user-defined type and the specialization satisfies all requirements for the original template, except where such specializations are prohibited." and "It is undefined behavior to declare a full specialization of any member function template of a standard library class or class template".
 â Jon Harper
 May 22 at 6:27
 
 
 
 
 |Â
show 3 more comments
up vote
3
down vote
Congrats, and good luck with your endeavors.
template <class T>
constexpr void assertCompleteType() noexcept
 using forced_complete_type = char[sizeof(T) ? 1 : -1];
 (void)sizeof(forced_complete_type);
Huh? A constexpr that doesnâÂÂt return a value, and an expression that does nothing?
I think you want to use static_assert, not a trick to cause an error in template instantiation.
static_assert (sizeof(T) > 0);
static_assert (std::is_convertible_v<T,U>);
inline UniquePointer(pointer p = nullptr) noexcept : d(p) 
inline UniquePointer(this_type && other) noexcept : d(other.take()) 
You donâÂÂt have to use inline when putting the body of the function inside the class.
Use the usual names for things: take should be release
Use =delete on the copy constructor and assignment operator, to prevent them from being automatically generated. Do provide a move assignment operator.
template <class U>
inline this_type &operator=(UniquePointer<U, Cleanup> &&other)
 noexcept(std::is_convertible<U, T>())
 qe::detail::assertConvertible<U, T>();
 if (*this != other)
 reset(other.take());
 return *this;
The is_convertible in the noexcept doesnâÂÂt make sense. It is non-throwing if what you do in the body will never throw. If this is a trick to get SFINAE tucked away, it will not work. Likewise, the assertConvertible does not belong in the body at all! You want this form of operator= to only match overloading if the conversion is possible.
That is, use std::enable_if, as an extra dummy template parameter.
Meanwhile, it doesnâÂÂt make sense to assign an int* to a long* without any kind of conversion going on. You want to check conversion ability on the pointers, not on the types being pointed to!
The style in C++ is to put the * or & with the type, not the identifier. This is called out specifically near the beginning of StroustrupâÂÂs first book, and is an intentional difference from C style.
 auto oldD = this->d;
 this->d = nullptr;
DonâÂÂt use this-> for normal member access.
In this function, you could just write return std::exchange (d, nullptr);. See CPPreference â you mentioned needing to get to know the libraries better; this is a good resource to keep at hand.
 
 
 
 
 
 
 Thank you for your review! 1. The constexpr is definitely a late-night mishap. However, the code itself (without- constexpr) ought to cause a template instantiation error if an incomplete type is provided. It's used immediately before- Cleanupis called. Is static_assert still the better method? 2. The use of- take()along with- release()is intended for Qt compatibility. =) 3. I had a bad feeling about the conversion code. Thank you for your insight.
 â Jon Harper
 May 21 at 7:29
 
 
 
 
 
 
 
 
 
 
 I just realized that because the deleter is not stored, swapping with a convertible type is out anyway. I could test for a virtual destructor, though...
 â Jon Harper
 May 21 at 9:48
 
 
 
 
 
 
 2
 
 
 
 
 - static_assertis the right way to cause a compile error on purpose if some condition is not met. However, you should use- enable_ifto prevent such uses in the first place.
 â JDà Âugosz
 May 21 at 12:17
 
 
 
 
 
 
 
 
 
 By the way, the- assertCompleteTypefunction (sans- constexpr) is an idiom used to allow forward-declared types to be stored as pointers. It's generalized from Qt's- QScopedPointerDeleter. I adapted it to remove dependency on Qt for this review.
 â Jon Harper
 May 21 at 18:21
 
 
 
 
 
 
 
 
 
 
 Forward-declared types can indeed be used for pointers. What good is choking if the type is incomplete instead? Hmm, you want to make sure it is not incomplete when you- deleteit? Anyway,- static_assert, as of C++11, obsoletes tricks for achieving the same end.
 â JDà Âugosz
 May 21 at 20:06
 
 
 
add a comment |Â
up vote
0
down vote
comparison operators
You donâÂÂt need so many forms of the comparison operators.
First of all, a single template for != will reverse the sense of any == that you have, so donâÂÂt write all of them twice.
Second, donâÂÂt write special forms for nullptr. You are discouraged from writing explicit tests against nullptr and instead use the contextual conversion to bool and operator!. In fact, before C++11 you could not have special forms for this.
If you have another value that is null, the general form will be used; so, it has to handle the null case.
You can however provide a non-explicit constructor that converts nullptr to the smart pointer type.
 
 
 3
 
 
 
 
 Why didn't you just add that to your previously posted review?
 â Deduplicator
 May 22 at 0:26
 
 
 
 
 
 
 
 
 
 @Deduplicator I thought the OP might not see it at the end of a long answer he already read. ThereâÂÂs no notification to the OP for edits.
 â JDà Âugosz
 May 22 at 0:49
 
 
 
 
 
 2
 
 
 
 
 But for comments, which you can leave in addition.
 â Deduplicator
 May 22 at 2:51
 
 
 
add a comment |Â
 3 Answers
 3
 
active
oldest
votes
 3 Answers
 3
 
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
4
down vote
accepted
Well, how about a radical idea:
Just use std::unique_ptr, the only extra-capability yours has is .addressOf(). Yes, you might have to write replacements for QScopedPointerPodDeleter and QScopedPointerDeleteLater if you use either, but that isn't hard:
struct FreeDeleter 
 static void operator()(void* p) noexcept free(p); 
;
struct DeleteLater 
 template <class T>
 static void operator()(T* p) noexcept p->deleteLater(); 
;
Anyway, now let's criticise how you wrote your own one:
- static_assertis the tool of choice for causing compile-time-errors if something doesn't check out at compile-time. Learn to use and love it.
- You can safely - deleteor- freea null-pointer. It's defined to do nothing.
- Inside the class-definition and out-of-line member-function-definition-bodies, you can use the injected name - UniquePointerto refer to refer to the class. No need to mention the template-parameters unless you want some unrelated class from the same template.
- Decorating an in-class-definition with - inlineis absolutely pointless.
- I'm not sure why you put that expression into the noexcept-specification of the templated ctor. You should either use it for SFINAE, or a - static_assert, but this makes no sense at all. I prefer the first:- template <class U, typename std::enable_if<std::is_convertible<U, T>()>::type>
 UniquePointer(UniquePointer<U, Cleanup> && other) noexcept
 : d(other.take())
- I suggest doing minimal work when move-assigning: Simply swap. 
- You know, simply delegating to what you already have written would simplify your templated move-assignment-operator: - template <class U>
 auto operator=(UniquePointer<U, Cleanup>&& other) noexcept
 -> decltype((*this = UniquePointer(other))) // The extra-parens are critical
 return (*this = UniquePointer(other));
- Copy-ctor and copy-assignment-operator are already implicitly deleted. 
- I wonder why you don't take advantage of the default-argument to - .reset()in the dtor.
- There is something really disturbing going on when you try to make your smart-pointer responsible for what it already is responsible for. It might make sense to assert that doesn't happen, but are you really sure that should silently do nothing? 
- Also in - .reset(), I got to wondering whether the deleter is responsible for handling null-pointers, or your smart-pointer, or both? You seem to really like testing for null-pointers everywhere, anyway the compiler should be able to deduplicate the code just fine.
- Your templated - .swap()is utterly useless. Again, the expression which should be used for SFINAE is in the- noexcept-specification, and you force a compile-time-error later when the body gets compiled instead, unless- Uis- T. In that case though, the non-templated version claims precedence.
- Write - operator==and- operator!=differently, and they will be far more comprehensive and useful:- template <class T, class D, class U>
 constexpr auto operator==(const UniquePointer<T, D>& a, const U& b) noexcept
 -> decltype(b == a.d)
 return b == a.d;
 template <class T, class D, class U>
 constexpr auto operator==(const U& a, const UniquePointer<T, D>& b) noexcept
 -> std::enable_if_t<std::is_pointer<U>() || std::is_null_pointer<U>(), decltype(a == b.d)>
 return a == b.d;- That also covers comparing with raw pointers, possible down-casting, and smart-pointers following the same pattern. 
- You should not put your own - swap()in- ::std. Even though it probably works, it's not allowed. Either rely on ADL to pick it up from the same namespace as your type, or depend on- std::swap()(which should be good enough anyway).
 
 
 
 
 
 
 re #6: the generic- std::swapwill use the classâÂÂs move assignment and move constructor, so do it the other way around: implement assignment, and let the library auto-generate- swap.
 â JDà Âugosz
 May 22 at 0:54
 
 
 
 
 
 
 
 
 
 a note on obsessively checking for null: use- gsl::not_nulland the compiler only inserts a check once, where needed.
 â JDà Âugosz
 May 22 at 0:57
 
 
 
 
 
 
 
 
 
 Regarding 0: why I don't use- unique_ptr? Because this is a hobby for me. I'm learning the motivations by studying implementations and trying my own hand at it. I have no professional aspirations. =) Oh, and I also originally started this before I had access to a C++11 compiler.
 â Jon Harper
 May 22 at 1:20
 
 
 
 
 
 
 
 
 
 
 @JonHarper: He has- .swap()already. Also, he could swap the only member.
 â Deduplicator
 May 22 at 2:56
 
 
 
 
 
 
 
 
 
 Re #14: Partial specializations of- stdtemplates are allowed: "It is allowed to add template specializations for any standard library template to the namespace std only if the declaration depends on a user-defined type and the specialization satisfies all requirements for the original template, except where such specializations are prohibited." and "It is undefined behavior to declare a full specialization of any member function template of a standard library class or class template".
 â Jon Harper
 May 22 at 6:27
 
 
 
 
 |Â
show 3 more comments
up vote
4
down vote
accepted
Well, how about a radical idea:
Just use std::unique_ptr, the only extra-capability yours has is .addressOf(). Yes, you might have to write replacements for QScopedPointerPodDeleter and QScopedPointerDeleteLater if you use either, but that isn't hard:
struct FreeDeleter 
 static void operator()(void* p) noexcept free(p); 
;
struct DeleteLater 
 template <class T>
 static void operator()(T* p) noexcept p->deleteLater(); 
;
Anyway, now let's criticise how you wrote your own one:
- static_assertis the tool of choice for causing compile-time-errors if something doesn't check out at compile-time. Learn to use and love it.
- You can safely - deleteor- freea null-pointer. It's defined to do nothing.
- Inside the class-definition and out-of-line member-function-definition-bodies, you can use the injected name - UniquePointerto refer to refer to the class. No need to mention the template-parameters unless you want some unrelated class from the same template.
- Decorating an in-class-definition with - inlineis absolutely pointless.
- I'm not sure why you put that expression into the noexcept-specification of the templated ctor. You should either use it for SFINAE, or a - static_assert, but this makes no sense at all. I prefer the first:- template <class U, typename std::enable_if<std::is_convertible<U, T>()>::type>
 UniquePointer(UniquePointer<U, Cleanup> && other) noexcept
 : d(other.take())
- I suggest doing minimal work when move-assigning: Simply swap. 
- You know, simply delegating to what you already have written would simplify your templated move-assignment-operator: - template <class U>
 auto operator=(UniquePointer<U, Cleanup>&& other) noexcept
 -> decltype((*this = UniquePointer(other))) // The extra-parens are critical
 return (*this = UniquePointer(other));
- Copy-ctor and copy-assignment-operator are already implicitly deleted. 
- I wonder why you don't take advantage of the default-argument to - .reset()in the dtor.
- There is something really disturbing going on when you try to make your smart-pointer responsible for what it already is responsible for. It might make sense to assert that doesn't happen, but are you really sure that should silently do nothing? 
- Also in - .reset(), I got to wondering whether the deleter is responsible for handling null-pointers, or your smart-pointer, or both? You seem to really like testing for null-pointers everywhere, anyway the compiler should be able to deduplicate the code just fine.
- Your templated - .swap()is utterly useless. Again, the expression which should be used for SFINAE is in the- noexcept-specification, and you force a compile-time-error later when the body gets compiled instead, unless- Uis- T. In that case though, the non-templated version claims precedence.
- Write - operator==and- operator!=differently, and they will be far more comprehensive and useful:- template <class T, class D, class U>
 constexpr auto operator==(const UniquePointer<T, D>& a, const U& b) noexcept
 -> decltype(b == a.d)
 return b == a.d;
 template <class T, class D, class U>
 constexpr auto operator==(const U& a, const UniquePointer<T, D>& b) noexcept
 -> std::enable_if_t<std::is_pointer<U>() || std::is_null_pointer<U>(), decltype(a == b.d)>
 return a == b.d;- That also covers comparing with raw pointers, possible down-casting, and smart-pointers following the same pattern. 
- You should not put your own - swap()in- ::std. Even though it probably works, it's not allowed. Either rely on ADL to pick it up from the same namespace as your type, or depend on- std::swap()(which should be good enough anyway).
 
 
 
 
 
 
 re #6: the generic- std::swapwill use the classâÂÂs move assignment and move constructor, so do it the other way around: implement assignment, and let the library auto-generate- swap.
 â JDà Âugosz
 May 22 at 0:54
 
 
 
 
 
 
 
 
 
 a note on obsessively checking for null: use- gsl::not_nulland the compiler only inserts a check once, where needed.
 â JDà Âugosz
 May 22 at 0:57
 
 
 
 
 
 
 
 
 
 Regarding 0: why I don't use- unique_ptr? Because this is a hobby for me. I'm learning the motivations by studying implementations and trying my own hand at it. I have no professional aspirations. =) Oh, and I also originally started this before I had access to a C++11 compiler.
 â Jon Harper
 May 22 at 1:20
 
 
 
 
 
 
 
 
 
 
 @JonHarper: He has- .swap()already. Also, he could swap the only member.
 â Deduplicator
 May 22 at 2:56
 
 
 
 
 
 
 
 
 
 Re #14: Partial specializations of- stdtemplates are allowed: "It is allowed to add template specializations for any standard library template to the namespace std only if the declaration depends on a user-defined type and the specialization satisfies all requirements for the original template, except where such specializations are prohibited." and "It is undefined behavior to declare a full specialization of any member function template of a standard library class or class template".
 â Jon Harper
 May 22 at 6:27
 
 
 
 
 |Â
show 3 more comments
up vote
4
down vote
accepted
up vote
4
down vote
accepted
Well, how about a radical idea:
Just use std::unique_ptr, the only extra-capability yours has is .addressOf(). Yes, you might have to write replacements for QScopedPointerPodDeleter and QScopedPointerDeleteLater if you use either, but that isn't hard:
struct FreeDeleter 
 static void operator()(void* p) noexcept free(p); 
;
struct DeleteLater 
 template <class T>
 static void operator()(T* p) noexcept p->deleteLater(); 
;
Anyway, now let's criticise how you wrote your own one:
- static_assertis the tool of choice for causing compile-time-errors if something doesn't check out at compile-time. Learn to use and love it.
- You can safely - deleteor- freea null-pointer. It's defined to do nothing.
- Inside the class-definition and out-of-line member-function-definition-bodies, you can use the injected name - UniquePointerto refer to refer to the class. No need to mention the template-parameters unless you want some unrelated class from the same template.
- Decorating an in-class-definition with - inlineis absolutely pointless.
- I'm not sure why you put that expression into the noexcept-specification of the templated ctor. You should either use it for SFINAE, or a - static_assert, but this makes no sense at all. I prefer the first:- template <class U, typename std::enable_if<std::is_convertible<U, T>()>::type>
 UniquePointer(UniquePointer<U, Cleanup> && other) noexcept
 : d(other.take())
- I suggest doing minimal work when move-assigning: Simply swap. 
- You know, simply delegating to what you already have written would simplify your templated move-assignment-operator: - template <class U>
 auto operator=(UniquePointer<U, Cleanup>&& other) noexcept
 -> decltype((*this = UniquePointer(other))) // The extra-parens are critical
 return (*this = UniquePointer(other));
- Copy-ctor and copy-assignment-operator are already implicitly deleted. 
- I wonder why you don't take advantage of the default-argument to - .reset()in the dtor.
- There is something really disturbing going on when you try to make your smart-pointer responsible for what it already is responsible for. It might make sense to assert that doesn't happen, but are you really sure that should silently do nothing? 
- Also in - .reset(), I got to wondering whether the deleter is responsible for handling null-pointers, or your smart-pointer, or both? You seem to really like testing for null-pointers everywhere, anyway the compiler should be able to deduplicate the code just fine.
- Your templated - .swap()is utterly useless. Again, the expression which should be used for SFINAE is in the- noexcept-specification, and you force a compile-time-error later when the body gets compiled instead, unless- Uis- T. In that case though, the non-templated version claims precedence.
- Write - operator==and- operator!=differently, and they will be far more comprehensive and useful:- template <class T, class D, class U>
 constexpr auto operator==(const UniquePointer<T, D>& a, const U& b) noexcept
 -> decltype(b == a.d)
 return b == a.d;
 template <class T, class D, class U>
 constexpr auto operator==(const U& a, const UniquePointer<T, D>& b) noexcept
 -> std::enable_if_t<std::is_pointer<U>() || std::is_null_pointer<U>(), decltype(a == b.d)>
 return a == b.d;- That also covers comparing with raw pointers, possible down-casting, and smart-pointers following the same pattern. 
- You should not put your own - swap()in- ::std. Even though it probably works, it's not allowed. Either rely on ADL to pick it up from the same namespace as your type, or depend on- std::swap()(which should be good enough anyway).
Well, how about a radical idea:
Just use std::unique_ptr, the only extra-capability yours has is .addressOf(). Yes, you might have to write replacements for QScopedPointerPodDeleter and QScopedPointerDeleteLater if you use either, but that isn't hard:
struct FreeDeleter 
 static void operator()(void* p) noexcept free(p); 
;
struct DeleteLater 
 template <class T>
 static void operator()(T* p) noexcept p->deleteLater(); 
;
Anyway, now let's criticise how you wrote your own one:
- static_assertis the tool of choice for causing compile-time-errors if something doesn't check out at compile-time. Learn to use and love it.
- You can safely - deleteor- freea null-pointer. It's defined to do nothing.
- Inside the class-definition and out-of-line member-function-definition-bodies, you can use the injected name - UniquePointerto refer to refer to the class. No need to mention the template-parameters unless you want some unrelated class from the same template.
- Decorating an in-class-definition with - inlineis absolutely pointless.
- I'm not sure why you put that expression into the noexcept-specification of the templated ctor. You should either use it for SFINAE, or a - static_assert, but this makes no sense at all. I prefer the first:- template <class U, typename std::enable_if<std::is_convertible<U, T>()>::type>
 UniquePointer(UniquePointer<U, Cleanup> && other) noexcept
 : d(other.take())
- I suggest doing minimal work when move-assigning: Simply swap. 
- You know, simply delegating to what you already have written would simplify your templated move-assignment-operator: - template <class U>
 auto operator=(UniquePointer<U, Cleanup>&& other) noexcept
 -> decltype((*this = UniquePointer(other))) // The extra-parens are critical
 return (*this = UniquePointer(other));
- Copy-ctor and copy-assignment-operator are already implicitly deleted. 
- I wonder why you don't take advantage of the default-argument to - .reset()in the dtor.
- There is something really disturbing going on when you try to make your smart-pointer responsible for what it already is responsible for. It might make sense to assert that doesn't happen, but are you really sure that should silently do nothing? 
- Also in - .reset(), I got to wondering whether the deleter is responsible for handling null-pointers, or your smart-pointer, or both? You seem to really like testing for null-pointers everywhere, anyway the compiler should be able to deduplicate the code just fine.
- Your templated - .swap()is utterly useless. Again, the expression which should be used for SFINAE is in the- noexcept-specification, and you force a compile-time-error later when the body gets compiled instead, unless- Uis- T. In that case though, the non-templated version claims precedence.
- Write - operator==and- operator!=differently, and they will be far more comprehensive and useful:- template <class T, class D, class U>
 constexpr auto operator==(const UniquePointer<T, D>& a, const U& b) noexcept
 -> decltype(b == a.d)
 return b == a.d;
 template <class T, class D, class U>
 constexpr auto operator==(const U& a, const UniquePointer<T, D>& b) noexcept
 -> std::enable_if_t<std::is_pointer<U>() || std::is_null_pointer<U>(), decltype(a == b.d)>
 return a == b.d;- That also covers comparing with raw pointers, possible down-casting, and smart-pointers following the same pattern. 
- You should not put your own - swap()in- ::std. Even though it probably works, it's not allowed. Either rely on ADL to pick it up from the same namespace as your type, or depend on- std::swap()(which should be good enough anyway).
answered May 22 at 0:21
Deduplicator
9,7511744
9,7511744
 
 
 
 
 
 
 re #6: the generic- std::swapwill use the classâÂÂs move assignment and move constructor, so do it the other way around: implement assignment, and let the library auto-generate- swap.
 â JDà Âugosz
 May 22 at 0:54
 
 
 
 
 
 
 
 
 
 a note on obsessively checking for null: use- gsl::not_nulland the compiler only inserts a check once, where needed.
 â JDà Âugosz
 May 22 at 0:57
 
 
 
 
 
 
 
 
 
 Regarding 0: why I don't use- unique_ptr? Because this is a hobby for me. I'm learning the motivations by studying implementations and trying my own hand at it. I have no professional aspirations. =) Oh, and I also originally started this before I had access to a C++11 compiler.
 â Jon Harper
 May 22 at 1:20
 
 
 
 
 
 
 
 
 
 
 @JonHarper: He has- .swap()already. Also, he could swap the only member.
 â Deduplicator
 May 22 at 2:56
 
 
 
 
 
 
 
 
 
 Re #14: Partial specializations of- stdtemplates are allowed: "It is allowed to add template specializations for any standard library template to the namespace std only if the declaration depends on a user-defined type and the specialization satisfies all requirements for the original template, except where such specializations are prohibited." and "It is undefined behavior to declare a full specialization of any member function template of a standard library class or class template".
 â Jon Harper
 May 22 at 6:27
 
 
 
 
 |Â
show 3 more comments
 
 
 
 
 
 
 re #6: the generic- std::swapwill use the classâÂÂs move assignment and move constructor, so do it the other way around: implement assignment, and let the library auto-generate- swap.
 â JDà Âugosz
 May 22 at 0:54
 
 
 
 
 
 
 
 
 
 a note on obsessively checking for null: use- gsl::not_nulland the compiler only inserts a check once, where needed.
 â JDà Âugosz
 May 22 at 0:57
 
 
 
 
 
 
 
 
 
 Regarding 0: why I don't use- unique_ptr? Because this is a hobby for me. I'm learning the motivations by studying implementations and trying my own hand at it. I have no professional aspirations. =) Oh, and I also originally started this before I had access to a C++11 compiler.
 â Jon Harper
 May 22 at 1:20
 
 
 
 
 
 
 
 
 
 
 @JonHarper: He has- .swap()already. Also, he could swap the only member.
 â Deduplicator
 May 22 at 2:56
 
 
 
 
 
 
 
 
 
 Re #14: Partial specializations of- stdtemplates are allowed: "It is allowed to add template specializations for any standard library template to the namespace std only if the declaration depends on a user-defined type and the specialization satisfies all requirements for the original template, except where such specializations are prohibited." and "It is undefined behavior to declare a full specialization of any member function template of a standard library class or class template".
 â Jon Harper
 May 22 at 6:27
 
 
 
 
re #6: the generic
std::swap will use the classâÂÂs move assignment and move constructor, so do it the other way around: implement assignment, and let the library auto-generate swap.â JDà Âugosz
May 22 at 0:54
re #6: the generic
std::swap will use the classâÂÂs move assignment and move constructor, so do it the other way around: implement assignment, and let the library auto-generate swap.â JDà Âugosz
May 22 at 0:54
a note on obsessively checking for null: use
gsl::not_null and the compiler only inserts a check once, where needed.â JDà Âugosz
May 22 at 0:57
a note on obsessively checking for null: use
gsl::not_null and the compiler only inserts a check once, where needed.â JDà Âugosz
May 22 at 0:57
Regarding 0: why I don't use
unique_ptr? Because this is a hobby for me. I'm learning the motivations by studying implementations and trying my own hand at it. I have no professional aspirations. =) Oh, and I also originally started this before I had access to a C++11 compiler.â Jon Harper
May 22 at 1:20
Regarding 0: why I don't use
unique_ptr? Because this is a hobby for me. I'm learning the motivations by studying implementations and trying my own hand at it. I have no professional aspirations. =) Oh, and I also originally started this before I had access to a C++11 compiler.â Jon Harper
May 22 at 1:20
@JonHarper: He has
.swap() already. Also, he could swap the only member.â Deduplicator
May 22 at 2:56
@JonHarper: He has
.swap() already. Also, he could swap the only member.â Deduplicator
May 22 at 2:56
Re #14: Partial specializations of
std templates are allowed: "It is allowed to add template specializations for any standard library template to the namespace std only if the declaration depends on a user-defined type and the specialization satisfies all requirements for the original template, except where such specializations are prohibited." and "It is undefined behavior to declare a full specialization of any member function template of a standard library class or class template".â Jon Harper
May 22 at 6:27
Re #14: Partial specializations of
std templates are allowed: "It is allowed to add template specializations for any standard library template to the namespace std only if the declaration depends on a user-defined type and the specialization satisfies all requirements for the original template, except where such specializations are prohibited." and "It is undefined behavior to declare a full specialization of any member function template of a standard library class or class template".â Jon Harper
May 22 at 6:27
 |Â
show 3 more comments
up vote
3
down vote
Congrats, and good luck with your endeavors.
template <class T>
constexpr void assertCompleteType() noexcept
 using forced_complete_type = char[sizeof(T) ? 1 : -1];
 (void)sizeof(forced_complete_type);
Huh? A constexpr that doesnâÂÂt return a value, and an expression that does nothing?
I think you want to use static_assert, not a trick to cause an error in template instantiation.
static_assert (sizeof(T) > 0);
static_assert (std::is_convertible_v<T,U>);
inline UniquePointer(pointer p = nullptr) noexcept : d(p) 
inline UniquePointer(this_type && other) noexcept : d(other.take()) 
You donâÂÂt have to use inline when putting the body of the function inside the class.
Use the usual names for things: take should be release
Use =delete on the copy constructor and assignment operator, to prevent them from being automatically generated. Do provide a move assignment operator.
template <class U>
inline this_type &operator=(UniquePointer<U, Cleanup> &&other)
 noexcept(std::is_convertible<U, T>())
 qe::detail::assertConvertible<U, T>();
 if (*this != other)
 reset(other.take());
 return *this;
The is_convertible in the noexcept doesnâÂÂt make sense. It is non-throwing if what you do in the body will never throw. If this is a trick to get SFINAE tucked away, it will not work. Likewise, the assertConvertible does not belong in the body at all! You want this form of operator= to only match overloading if the conversion is possible.
That is, use std::enable_if, as an extra dummy template parameter.
Meanwhile, it doesnâÂÂt make sense to assign an int* to a long* without any kind of conversion going on. You want to check conversion ability on the pointers, not on the types being pointed to!
The style in C++ is to put the * or & with the type, not the identifier. This is called out specifically near the beginning of StroustrupâÂÂs first book, and is an intentional difference from C style.
 auto oldD = this->d;
 this->d = nullptr;
DonâÂÂt use this-> for normal member access.
In this function, you could just write return std::exchange (d, nullptr);. See CPPreference â you mentioned needing to get to know the libraries better; this is a good resource to keep at hand.
 
 
 
 
 
 
 Thank you for your review! 1. The constexpr is definitely a late-night mishap. However, the code itself (without- constexpr) ought to cause a template instantiation error if an incomplete type is provided. It's used immediately before- Cleanupis called. Is static_assert still the better method? 2. The use of- take()along with- release()is intended for Qt compatibility. =) 3. I had a bad feeling about the conversion code. Thank you for your insight.
 â Jon Harper
 May 21 at 7:29
 
 
 
 
 
 
 
 
 
 
 I just realized that because the deleter is not stored, swapping with a convertible type is out anyway. I could test for a virtual destructor, though...
 â Jon Harper
 May 21 at 9:48
 
 
 
 
 
 
 2
 
 
 
 
 - static_assertis the right way to cause a compile error on purpose if some condition is not met. However, you should use- enable_ifto prevent such uses in the first place.
 â JDà Âugosz
 May 21 at 12:17
 
 
 
 
 
 
 
 
 
 By the way, the- assertCompleteTypefunction (sans- constexpr) is an idiom used to allow forward-declared types to be stored as pointers. It's generalized from Qt's- QScopedPointerDeleter. I adapted it to remove dependency on Qt for this review.
 â Jon Harper
 May 21 at 18:21
 
 
 
 
 
 
 
 
 
 
 Forward-declared types can indeed be used for pointers. What good is choking if the type is incomplete instead? Hmm, you want to make sure it is not incomplete when you- deleteit? Anyway,- static_assert, as of C++11, obsoletes tricks for achieving the same end.
 â JDà Âugosz
 May 21 at 20:06
 
 
 
add a comment |Â
up vote
3
down vote
Congrats, and good luck with your endeavors.
template <class T>
constexpr void assertCompleteType() noexcept
 using forced_complete_type = char[sizeof(T) ? 1 : -1];
 (void)sizeof(forced_complete_type);
Huh? A constexpr that doesnâÂÂt return a value, and an expression that does nothing?
I think you want to use static_assert, not a trick to cause an error in template instantiation.
static_assert (sizeof(T) > 0);
static_assert (std::is_convertible_v<T,U>);
inline UniquePointer(pointer p = nullptr) noexcept : d(p) 
inline UniquePointer(this_type && other) noexcept : d(other.take()) 
You donâÂÂt have to use inline when putting the body of the function inside the class.
Use the usual names for things: take should be release
Use =delete on the copy constructor and assignment operator, to prevent them from being automatically generated. Do provide a move assignment operator.
template <class U>
inline this_type &operator=(UniquePointer<U, Cleanup> &&other)
 noexcept(std::is_convertible<U, T>())
 qe::detail::assertConvertible<U, T>();
 if (*this != other)
 reset(other.take());
 return *this;
The is_convertible in the noexcept doesnâÂÂt make sense. It is non-throwing if what you do in the body will never throw. If this is a trick to get SFINAE tucked away, it will not work. Likewise, the assertConvertible does not belong in the body at all! You want this form of operator= to only match overloading if the conversion is possible.
That is, use std::enable_if, as an extra dummy template parameter.
Meanwhile, it doesnâÂÂt make sense to assign an int* to a long* without any kind of conversion going on. You want to check conversion ability on the pointers, not on the types being pointed to!
The style in C++ is to put the * or & with the type, not the identifier. This is called out specifically near the beginning of StroustrupâÂÂs first book, and is an intentional difference from C style.
 auto oldD = this->d;
 this->d = nullptr;
DonâÂÂt use this-> for normal member access.
In this function, you could just write return std::exchange (d, nullptr);. See CPPreference â you mentioned needing to get to know the libraries better; this is a good resource to keep at hand.
 
 
 
 
 
 
 Thank you for your review! 1. The constexpr is definitely a late-night mishap. However, the code itself (without- constexpr) ought to cause a template instantiation error if an incomplete type is provided. It's used immediately before- Cleanupis called. Is static_assert still the better method? 2. The use of- take()along with- release()is intended for Qt compatibility. =) 3. I had a bad feeling about the conversion code. Thank you for your insight.
 â Jon Harper
 May 21 at 7:29
 
 
 
 
 
 
 
 
 
 
 I just realized that because the deleter is not stored, swapping with a convertible type is out anyway. I could test for a virtual destructor, though...
 â Jon Harper
 May 21 at 9:48
 
 
 
 
 
 
 2
 
 
 
 
 - static_assertis the right way to cause a compile error on purpose if some condition is not met. However, you should use- enable_ifto prevent such uses in the first place.
 â JDà Âugosz
 May 21 at 12:17
 
 
 
 
 
 
 
 
 
 By the way, the- assertCompleteTypefunction (sans- constexpr) is an idiom used to allow forward-declared types to be stored as pointers. It's generalized from Qt's- QScopedPointerDeleter. I adapted it to remove dependency on Qt for this review.
 â Jon Harper
 May 21 at 18:21
 
 
 
 
 
 
 
 
 
 
 Forward-declared types can indeed be used for pointers. What good is choking if the type is incomplete instead? Hmm, you want to make sure it is not incomplete when you- deleteit? Anyway,- static_assert, as of C++11, obsoletes tricks for achieving the same end.
 â JDà Âugosz
 May 21 at 20:06
 
 
 
add a comment |Â
up vote
3
down vote
up vote
3
down vote
Congrats, and good luck with your endeavors.
template <class T>
constexpr void assertCompleteType() noexcept
 using forced_complete_type = char[sizeof(T) ? 1 : -1];
 (void)sizeof(forced_complete_type);
Huh? A constexpr that doesnâÂÂt return a value, and an expression that does nothing?
I think you want to use static_assert, not a trick to cause an error in template instantiation.
static_assert (sizeof(T) > 0);
static_assert (std::is_convertible_v<T,U>);
inline UniquePointer(pointer p = nullptr) noexcept : d(p) 
inline UniquePointer(this_type && other) noexcept : d(other.take()) 
You donâÂÂt have to use inline when putting the body of the function inside the class.
Use the usual names for things: take should be release
Use =delete on the copy constructor and assignment operator, to prevent them from being automatically generated. Do provide a move assignment operator.
template <class U>
inline this_type &operator=(UniquePointer<U, Cleanup> &&other)
 noexcept(std::is_convertible<U, T>())
 qe::detail::assertConvertible<U, T>();
 if (*this != other)
 reset(other.take());
 return *this;
The is_convertible in the noexcept doesnâÂÂt make sense. It is non-throwing if what you do in the body will never throw. If this is a trick to get SFINAE tucked away, it will not work. Likewise, the assertConvertible does not belong in the body at all! You want this form of operator= to only match overloading if the conversion is possible.
That is, use std::enable_if, as an extra dummy template parameter.
Meanwhile, it doesnâÂÂt make sense to assign an int* to a long* without any kind of conversion going on. You want to check conversion ability on the pointers, not on the types being pointed to!
The style in C++ is to put the * or & with the type, not the identifier. This is called out specifically near the beginning of StroustrupâÂÂs first book, and is an intentional difference from C style.
 auto oldD = this->d;
 this->d = nullptr;
DonâÂÂt use this-> for normal member access.
In this function, you could just write return std::exchange (d, nullptr);. See CPPreference â you mentioned needing to get to know the libraries better; this is a good resource to keep at hand.
Congrats, and good luck with your endeavors.
template <class T>
constexpr void assertCompleteType() noexcept
 using forced_complete_type = char[sizeof(T) ? 1 : -1];
 (void)sizeof(forced_complete_type);
Huh? A constexpr that doesnâÂÂt return a value, and an expression that does nothing?
I think you want to use static_assert, not a trick to cause an error in template instantiation.
static_assert (sizeof(T) > 0);
static_assert (std::is_convertible_v<T,U>);
inline UniquePointer(pointer p = nullptr) noexcept : d(p) 
inline UniquePointer(this_type && other) noexcept : d(other.take()) 
You donâÂÂt have to use inline when putting the body of the function inside the class.
Use the usual names for things: take should be release
Use =delete on the copy constructor and assignment operator, to prevent them from being automatically generated. Do provide a move assignment operator.
template <class U>
inline this_type &operator=(UniquePointer<U, Cleanup> &&other)
 noexcept(std::is_convertible<U, T>())
 qe::detail::assertConvertible<U, T>();
 if (*this != other)
 reset(other.take());
 return *this;
The is_convertible in the noexcept doesnâÂÂt make sense. It is non-throwing if what you do in the body will never throw. If this is a trick to get SFINAE tucked away, it will not work. Likewise, the assertConvertible does not belong in the body at all! You want this form of operator= to only match overloading if the conversion is possible.
That is, use std::enable_if, as an extra dummy template parameter.
Meanwhile, it doesnâÂÂt make sense to assign an int* to a long* without any kind of conversion going on. You want to check conversion ability on the pointers, not on the types being pointed to!
The style in C++ is to put the * or & with the type, not the identifier. This is called out specifically near the beginning of StroustrupâÂÂs first book, and is an intentional difference from C style.
 auto oldD = this->d;
 this->d = nullptr;
DonâÂÂt use this-> for normal member access.
In this function, you could just write return std::exchange (d, nullptr);. See CPPreference â you mentioned needing to get to know the libraries better; this is a good resource to keep at hand.
answered May 21 at 5:48


JDÃ Âugosz
5,047731
5,047731
 
 
 
 
 
 
 Thank you for your review! 1. The constexpr is definitely a late-night mishap. However, the code itself (without- constexpr) ought to cause a template instantiation error if an incomplete type is provided. It's used immediately before- Cleanupis called. Is static_assert still the better method? 2. The use of- take()along with- release()is intended for Qt compatibility. =) 3. I had a bad feeling about the conversion code. Thank you for your insight.
 â Jon Harper
 May 21 at 7:29
 
 
 
 
 
 
 
 
 
 
 I just realized that because the deleter is not stored, swapping with a convertible type is out anyway. I could test for a virtual destructor, though...
 â Jon Harper
 May 21 at 9:48
 
 
 
 
 
 
 2
 
 
 
 
 - static_assertis the right way to cause a compile error on purpose if some condition is not met. However, you should use- enable_ifto prevent such uses in the first place.
 â JDà Âugosz
 May 21 at 12:17
 
 
 
 
 
 
 
 
 
 By the way, the- assertCompleteTypefunction (sans- constexpr) is an idiom used to allow forward-declared types to be stored as pointers. It's generalized from Qt's- QScopedPointerDeleter. I adapted it to remove dependency on Qt for this review.
 â Jon Harper
 May 21 at 18:21
 
 
 
 
 
 
 
 
 
 
 Forward-declared types can indeed be used for pointers. What good is choking if the type is incomplete instead? Hmm, you want to make sure it is not incomplete when you- deleteit? Anyway,- static_assert, as of C++11, obsoletes tricks for achieving the same end.
 â JDà Âugosz
 May 21 at 20:06
 
 
 
add a comment |Â
 
 
 
 
 
 
 Thank you for your review! 1. The constexpr is definitely a late-night mishap. However, the code itself (without- constexpr) ought to cause a template instantiation error if an incomplete type is provided. It's used immediately before- Cleanupis called. Is static_assert still the better method? 2. The use of- take()along with- release()is intended for Qt compatibility. =) 3. I had a bad feeling about the conversion code. Thank you for your insight.
 â Jon Harper
 May 21 at 7:29
 
 
 
 
 
 
 
 
 
 
 I just realized that because the deleter is not stored, swapping with a convertible type is out anyway. I could test for a virtual destructor, though...
 â Jon Harper
 May 21 at 9:48
 
 
 
 
 
 
 2
 
 
 
 
 - static_assertis the right way to cause a compile error on purpose if some condition is not met. However, you should use- enable_ifto prevent such uses in the first place.
 â JDà Âugosz
 May 21 at 12:17
 
 
 
 
 
 
 
 
 
 By the way, the- assertCompleteTypefunction (sans- constexpr) is an idiom used to allow forward-declared types to be stored as pointers. It's generalized from Qt's- QScopedPointerDeleter. I adapted it to remove dependency on Qt for this review.
 â Jon Harper
 May 21 at 18:21
 
 
 
 
 
 
 
 
 
 
 Forward-declared types can indeed be used for pointers. What good is choking if the type is incomplete instead? Hmm, you want to make sure it is not incomplete when you- deleteit? Anyway,- static_assert, as of C++11, obsoletes tricks for achieving the same end.
 â JDà Âugosz
 May 21 at 20:06
 
 
 
Thank you for your review! 1. The constexpr is definitely a late-night mishap. However, the code itself (without
constexpr) ought to cause a template instantiation error if an incomplete type is provided. It's used immediately before Cleanup is called. Is static_assert still the better method? 2. The use of take() along with release() is intended for Qt compatibility. =) 3. I had a bad feeling about the conversion code. Thank you for your insight.â Jon Harper
May 21 at 7:29
Thank you for your review! 1. The constexpr is definitely a late-night mishap. However, the code itself (without
constexpr) ought to cause a template instantiation error if an incomplete type is provided. It's used immediately before Cleanup is called. Is static_assert still the better method? 2. The use of take() along with release() is intended for Qt compatibility. =) 3. I had a bad feeling about the conversion code. Thank you for your insight.â Jon Harper
May 21 at 7:29
I just realized that because the deleter is not stored, swapping with a convertible type is out anyway. I could test for a virtual destructor, though...
â Jon Harper
May 21 at 9:48
I just realized that because the deleter is not stored, swapping with a convertible type is out anyway. I could test for a virtual destructor, though...
â Jon Harper
May 21 at 9:48
2
2
static_assert is the right way to cause a compile error on purpose if some condition is not met. However, you should useenable_if to prevent such uses in the first place.â JDà Âugosz
May 21 at 12:17
static_assert is the right way to cause a compile error on purpose if some condition is not met. However, you should useenable_if to prevent such uses in the first place.â JDà Âugosz
May 21 at 12:17
By the way, the
assertCompleteType function (sans constexpr) is an idiom used to allow forward-declared types to be stored as pointers. It's generalized from Qt's QScopedPointerDeleter. I adapted it to remove dependency on Qt for this review.â Jon Harper
May 21 at 18:21
By the way, the
assertCompleteType function (sans constexpr) is an idiom used to allow forward-declared types to be stored as pointers. It's generalized from Qt's QScopedPointerDeleter. I adapted it to remove dependency on Qt for this review.â Jon Harper
May 21 at 18:21
Forward-declared types can indeed be used for pointers. What good is choking if the type is incomplete instead? Hmm, you want to make sure it is not incomplete when you
delete it? Anyway, static_assert, as of C++11, obsoletes tricks for achieving the same end.â JDà Âugosz
May 21 at 20:06
Forward-declared types can indeed be used for pointers. What good is choking if the type is incomplete instead? Hmm, you want to make sure it is not incomplete when you
delete it? Anyway, static_assert, as of C++11, obsoletes tricks for achieving the same end.â JDà Âugosz
May 21 at 20:06
add a comment |Â
up vote
0
down vote
comparison operators
You donâÂÂt need so many forms of the comparison operators.
First of all, a single template for != will reverse the sense of any == that you have, so donâÂÂt write all of them twice.
Second, donâÂÂt write special forms for nullptr. You are discouraged from writing explicit tests against nullptr and instead use the contextual conversion to bool and operator!. In fact, before C++11 you could not have special forms for this.
If you have another value that is null, the general form will be used; so, it has to handle the null case.
You can however provide a non-explicit constructor that converts nullptr to the smart pointer type.
 
 
 3
 
 
 
 
 Why didn't you just add that to your previously posted review?
 â Deduplicator
 May 22 at 0:26
 
 
 
 
 
 
 
 
 
 @Deduplicator I thought the OP might not see it at the end of a long answer he already read. ThereâÂÂs no notification to the OP for edits.
 â JDà Âugosz
 May 22 at 0:49
 
 
 
 
 
 2
 
 
 
 
 But for comments, which you can leave in addition.
 â Deduplicator
 May 22 at 2:51
 
 
 
add a comment |Â
up vote
0
down vote
comparison operators
You donâÂÂt need so many forms of the comparison operators.
First of all, a single template for != will reverse the sense of any == that you have, so donâÂÂt write all of them twice.
Second, donâÂÂt write special forms for nullptr. You are discouraged from writing explicit tests against nullptr and instead use the contextual conversion to bool and operator!. In fact, before C++11 you could not have special forms for this.
If you have another value that is null, the general form will be used; so, it has to handle the null case.
You can however provide a non-explicit constructor that converts nullptr to the smart pointer type.
 
 
 3
 
 
 
 
 Why didn't you just add that to your previously posted review?
 â Deduplicator
 May 22 at 0:26
 
 
 
 
 
 
 
 
 
 @Deduplicator I thought the OP might not see it at the end of a long answer he already read. ThereâÂÂs no notification to the OP for edits.
 â JDà Âugosz
 May 22 at 0:49
 
 
 
 
 
 2
 
 
 
 
 But for comments, which you can leave in addition.
 â Deduplicator
 May 22 at 2:51
 
 
 
add a comment |Â
up vote
0
down vote
up vote
0
down vote
comparison operators
You donâÂÂt need so many forms of the comparison operators.
First of all, a single template for != will reverse the sense of any == that you have, so donâÂÂt write all of them twice.
Second, donâÂÂt write special forms for nullptr. You are discouraged from writing explicit tests against nullptr and instead use the contextual conversion to bool and operator!. In fact, before C++11 you could not have special forms for this.
If you have another value that is null, the general form will be used; so, it has to handle the null case.
You can however provide a non-explicit constructor that converts nullptr to the smart pointer type.
comparison operators
You donâÂÂt need so many forms of the comparison operators.
First of all, a single template for != will reverse the sense of any == that you have, so donâÂÂt write all of them twice.
Second, donâÂÂt write special forms for nullptr. You are discouraged from writing explicit tests against nullptr and instead use the contextual conversion to bool and operator!. In fact, before C++11 you could not have special forms for this.
If you have another value that is null, the general form will be used; so, it has to handle the null case.
You can however provide a non-explicit constructor that converts nullptr to the smart pointer type.
answered May 21 at 20:12


JDÃ Âugosz
5,047731
5,047731
 
 
 3
 
 
 
 
 Why didn't you just add that to your previously posted review?
 â Deduplicator
 May 22 at 0:26
 
 
 
 
 
 
 
 
 
 @Deduplicator I thought the OP might not see it at the end of a long answer he already read. ThereâÂÂs no notification to the OP for edits.
 â JDà Âugosz
 May 22 at 0:49
 
 
 
 
 
 2
 
 
 
 
 But for comments, which you can leave in addition.
 â Deduplicator
 May 22 at 2:51
 
 
 
add a comment |Â
 
 
 3
 
 
 
 
 Why didn't you just add that to your previously posted review?
 â Deduplicator
 May 22 at 0:26
 
 
 
 
 
 
 
 
 
 @Deduplicator I thought the OP might not see it at the end of a long answer he already read. ThereâÂÂs no notification to the OP for edits.
 â JDà Âugosz
 May 22 at 0:49
 
 
 
 
 
 2
 
 
 
 
 But for comments, which you can leave in addition.
 â Deduplicator
 May 22 at 2:51
 
 
 
3
3
Why didn't you just add that to your previously posted review?
â Deduplicator
May 22 at 0:26
Why didn't you just add that to your previously posted review?
â Deduplicator
May 22 at 0:26
@Deduplicator I thought the OP might not see it at the end of a long answer he already read. ThereâÂÂs no notification to the OP for edits.
â JDà Âugosz
May 22 at 0:49
@Deduplicator I thought the OP might not see it at the end of a long answer he already read. ThereâÂÂs no notification to the OP for edits.
â JDà Âugosz
May 22 at 0:49
2
2
But for comments, which you can leave in addition.
â Deduplicator
May 22 at 2:51
But for comments, which you can leave in addition.
â Deduplicator
May 22 at 2:51
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f194825%2fa-moveable-qscopedpointer-or-a-qt-and-std-cross-compatible-unique-pointer%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
2
Welcome to Code Review, and congratulations on writing a well-written question on your first go.
â Mast
May 20 at 22:59