A moveable QScopedPointer, or a Qt and std cross-compatible unique_pointer
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::hash
andstd::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::UniquePointer
acts 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
using
declarations? Is my partial specialization ofstd::hash
correctly 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::hash
andstd::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::UniquePointer
acts 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
using
declarations? Is my partial specialization ofstd::hash
correctly 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::hash
andstd::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::UniquePointer
acts 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
using
declarations? Is my partial specialization ofstd::hash
correctly 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::hash
andstd::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::UniquePointer
acts 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
using
declarations? Is my partial specialization ofstd::hash
correctly 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_assert
is 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
delete
orfree
a 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
UniquePointer
to 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
inline
is 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 thenoexcept
-specification, and you force a compile-time-error later when the body gets compiled instead, unlessU
isT
. In that case though, the non-templated version claims precedence.Write
operator==
andoperator!=
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 onstd::swap()
(which should be good enough anyway).
re #6: the genericstd::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-generateswap
.
â JDà Âugosz
May 22 at 0:54
a note on obsessively checking for null: usegsl::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 useunique_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 ofstd
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 (withoutconstexpr
) ought to cause a template instantiation error if an incomplete type is provided. It's used immediately beforeCleanup
is called. Is static_assert still the better method? 2. The use oftake()
along withrelease()
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_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, theassertCompleteType
function (sansconstexpr
) is an idiom used to allow forward-declared types to be stored as pointers. It's generalized from Qt'sQScopedPointerDeleter
. 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 youdelete
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 |Â
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_assert
is 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
delete
orfree
a 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
UniquePointer
to 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
inline
is 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 thenoexcept
-specification, and you force a compile-time-error later when the body gets compiled instead, unlessU
isT
. In that case though, the non-templated version claims precedence.Write
operator==
andoperator!=
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 onstd::swap()
(which should be good enough anyway).
re #6: the genericstd::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-generateswap
.
â JDà Âugosz
May 22 at 0:54
a note on obsessively checking for null: usegsl::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 useunique_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 ofstd
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
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_assert
is 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
delete
orfree
a 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
UniquePointer
to 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
inline
is 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 thenoexcept
-specification, and you force a compile-time-error later when the body gets compiled instead, unlessU
isT
. In that case though, the non-templated version claims precedence.Write
operator==
andoperator!=
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 onstd::swap()
(which should be good enough anyway).
re #6: the genericstd::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-generateswap
.
â JDà Âugosz
May 22 at 0:54
a note on obsessively checking for null: usegsl::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 useunique_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 ofstd
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
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_assert
is 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
delete
orfree
a 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
UniquePointer
to 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
inline
is 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 thenoexcept
-specification, and you force a compile-time-error later when the body gets compiled instead, unlessU
isT
. In that case though, the non-templated version claims precedence.Write
operator==
andoperator!=
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 onstd::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_assert
is 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
delete
orfree
a 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
UniquePointer
to 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
inline
is 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 thenoexcept
-specification, and you force a compile-time-error later when the body gets compiled instead, unlessU
isT
. In that case though, the non-templated version claims precedence.Write
operator==
andoperator!=
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 onstd::swap()
(which should be good enough anyway).
answered May 22 at 0:21
Deduplicator
9,7511744
9,7511744
re #6: the genericstd::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-generateswap
.
â JDà Âugosz
May 22 at 0:54
a note on obsessively checking for null: usegsl::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 useunique_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 ofstd
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
re #6: the genericstd::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-generateswap
.
â JDà Âugosz
May 22 at 0:54
a note on obsessively checking for null: usegsl::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 useunique_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 ofstd
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 #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 (withoutconstexpr
) ought to cause a template instantiation error if an incomplete type is provided. It's used immediately beforeCleanup
is called. Is static_assert still the better method? 2. The use oftake()
along withrelease()
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_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, theassertCompleteType
function (sansconstexpr
) is an idiom used to allow forward-declared types to be stored as pointers. It's generalized from Qt'sQScopedPointerDeleter
. 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 youdelete
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
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 (withoutconstexpr
) ought to cause a template instantiation error if an incomplete type is provided. It's used immediately beforeCleanup
is called. Is static_assert still the better method? 2. The use oftake()
along withrelease()
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_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, theassertCompleteType
function (sansconstexpr
) is an idiom used to allow forward-declared types to be stored as pointers. It's generalized from Qt'sQScopedPointerDeleter
. 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 youdelete
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
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 (withoutconstexpr
) ought to cause a template instantiation error if an incomplete type is provided. It's used immediately beforeCleanup
is called. Is static_assert still the better method? 2. The use oftake()
along withrelease()
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_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, theassertCompleteType
function (sansconstexpr
) is an idiom used to allow forward-declared types to be stored as pointers. It's generalized from Qt'sQScopedPointerDeleter
. 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 youdelete
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 |Â
Thank you for your review! 1. The constexpr is definitely a late-night mishap. However, the code itself (withoutconstexpr
) ought to cause a template instantiation error if an incomplete type is provided. It's used immediately beforeCleanup
is called. Is static_assert still the better method? 2. The use oftake()
along withrelease()
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_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, theassertCompleteType
function (sansconstexpr
) is an idiom used to allow forward-declared types to be stored as pointers. It's generalized from Qt'sQScopedPointerDeleter
. 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 youdelete
it? 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