Introduce helper class QTaggedPointer

Useful for attaching small bits of information in the alignment bits of
a naked pointer. For use in the new property system as well as in
qtdeclarative (where currently a similar class exists as private API).

Change-Id: Idf9b93e714e15129f302e16425dbeda94bcd207b
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
This commit is contained in:
Tor Arne Vestbø 2017-05-02 19:23:13 +02:00 committed by Fabian Kosmale
parent 7ef3826497
commit 165de10027
12 changed files with 684 additions and 0 deletions

View File

@ -214,6 +214,7 @@ qt_add_module(Core
tools/qsimd.cpp tools/qsimd_p.h
tools/qsize.cpp tools/qsize.h
tools/qstack.h
tools/qtaggedpointer_p.h
tools/qtools_p.h
tools/qvarlengtharray.h
tools/qvector.h

View File

@ -230,6 +230,7 @@ qt_add_module(Core
tools/qsimd.cpp tools/qsimd_p.h
tools/qsize.cpp tools/qsize.h
tools/qstack.h
tools/qtaggedpointer.h
tools/qtools_p.h
tools/qvarlengtharray.h
tools/qvector.h

View File

@ -356,6 +356,16 @@ inline QDebug operator<<(QDebug debug, const QSharedPointer<T> &ptr)
return debug;
}
template <typename T, typename Tag> class QTaggedPointer;
template <typename T, typename Tag>
inline QDebug operator<<(QDebug debug, const QTaggedPointer<T, Tag> &ptr)
{
QDebugStateSaver saver(debug);
debug.nospace() << "QTaggedPointer(" << ptr.pointer() << ", " << ptr.tag() << ")";
return debug;
}
Q_CORE_EXPORT void qt_QMetaEnum_flagDebugOperator(QDebug &debug, size_t sizeofT, int value);
template <typename Int>

View File

@ -0,0 +1,207 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTAGGEDPOINTER_H
#define QTAGGEDPOINTER_H
#include <QtCore/qglobal.h>
#include <QtCore/qalgorithms.h>
#include <QtCore/qmath.h>
#include <limits.h>
QT_BEGIN_NAMESPACE
namespace QtPrivate {
constexpr quint8 nextByteSize(quint8 bits) { return (bits + 7) / 8; }
template <typename T>
struct TagInfo
{
static constexpr size_t alignment = alignof(T);
static_assert((alignment & (alignment - 1)) == 0,
"Alignment of template parameter must be power of two");
static constexpr quint8 tagBits = QtPrivate::qConstexprCountTrailingZeroBits(alignment);
static_assert(tagBits > 0,
"Alignment of template parameter does not allow any tags");
static constexpr size_t tagSize = QtPrivate::qConstexprNextPowerOfTwo(nextByteSize(tagBits));
static_assert(tagSize < sizeof(quintptr),
"Alignment of template parameter allows tags masking away pointer");
using TagType = typename QIntegerForSize<tagSize>::Unsigned;
};
}
template <typename T, typename Tag = typename QtPrivate::TagInfo<T>::TagType>
class QTaggedPointer
{
static constexpr quintptr tagMask = QtPrivate::TagInfo<T>::alignment - 1;
static constexpr quintptr pointerMask = ~tagMask;
using TagInternalType = typename QtPrivate::TagInfo<T>::TagType;
public:
using Type = T;
using TagType = Tag;
explicit QTaggedPointer(Type *pointer = nullptr, TagType tag = TagType()) noexcept
: d(quintptr(pointer))
{
Q_STATIC_ASSERT(sizeof(Type*) == sizeof(QTaggedPointer<Type>));
Q_ASSERT_X((quintptr(pointer) & tagMask) == 0,
"QTaggedPointer<T, Tag>", "Pointer is not aligned");
setTag(tag);
}
Type &operator*() const
{
Q_ASSERT(pointer());
return *pointer();
}
Type *operator->() const noexcept
{
return pointer();
}
explicit operator bool() const
{
return !isNull();
}
QTaggedPointer<T, Tag> &operator=(T *other) noexcept
{
d = reinterpret_cast<quintptr>(other) | (d & tagMask);
return *this;
}
QTaggedPointer<T, Tag> &operator=(std::nullptr_t) noexcept
{
d &= tagMask;
return *this;
}
static constexpr TagType maximumTag() noexcept
{
return TagType(TagInternalType(tagMask));
}
void setTag(TagType tag)
{
Q_ASSERT_X((static_cast<TagInternalType>(tag) & pointerMask) == 0,
"QTaggedPointer<T, Tag>::setTag", "Tag is larger than allowed by number of available tag bits");
d = (d & pointerMask) | (static_cast<TagInternalType>(tag) & tagMask);
}
TagType tag() const noexcept
{
return TagType(TagInternalType(d & tagMask));
}
Type* pointer() const noexcept
{
return reinterpret_cast<T*>(d & pointerMask);
}
bool isNull() const noexcept
{
return !pointer();
}
void swap(QTaggedPointer<Type, Tag> &other) noexcept
{
qSwap(d, other.d);
}
friend inline bool operator==(const QTaggedPointer<T, Tag> &lhs, const QTaggedPointer<T, Tag> &rhs) noexcept
{
return lhs.pointer() == rhs.pointer();
}
friend inline bool operator!=(const QTaggedPointer<T, Tag> &lhs, const QTaggedPointer<T, Tag> &rhs) noexcept
{
return lhs.pointer() != rhs.pointer();
}
friend inline bool operator==(const QTaggedPointer<T, Tag> &lhs, std::nullptr_t) noexcept
{
return lhs.isNull();
}
friend inline bool operator==(std::nullptr_t, const QTaggedPointer<T, Tag> &rhs) noexcept
{
return rhs.isNull();
}
friend inline bool operator!=(const QTaggedPointer<T, Tag> &lhs, std::nullptr_t) noexcept
{
return !lhs.isNull();
}
friend inline bool operator!=(std::nullptr_t, const QTaggedPointer<T, Tag> &rhs) noexcept
{
return !rhs.isNull();
}
friend inline bool operator!(const QTaggedPointer<T, Tag> &ptr) noexcept
{
return !ptr.pointer();
}
protected:
quintptr d;
};
template <typename T>
Q_DECLARE_TYPEINFO_BODY(QTaggedPointer<T>, Q_MOVABLE_TYPE);
template <typename T, typename Tag>
inline void swap(QTaggedPointer<T, Tag> &p1, QTaggedPointer<T, Tag> &p2) noexcept
{
p1.swap(p2);
}
QT_END_NAMESPACE
#endif // QTAGGEDPOINTER_H

View File

@ -44,6 +44,7 @@ HEADERS += \
tools/qsize.h \
tools/qstack.h \
tools/qtools_p.h \
tools/qtaggedpointer.h \
tools/qvarlengtharray.h \
tools/qvector.h \
tools/qversionnumber.h

View File

@ -0,0 +1,45 @@
# Generated from tools.pro.
add_subdirectory(collections)
add_subdirectory(containerapisymmetry)
add_subdirectory(qalgorithms)
add_subdirectory(qarraydata)
add_subdirectory(qbitarray)
add_subdirectory(qcache)
add_subdirectory(qcommandlineparser)
add_subdirectory(qcontiguouscache)
add_subdirectory(qcryptographichash)
add_subdirectory(qeasingcurve)
add_subdirectory(qexplicitlyshareddatapointer)
add_subdirectory(qflatmap)
add_subdirectory(qfreelist)
add_subdirectory(qhash)
add_subdirectory(qhashfunctions)
add_subdirectory(qline)
add_subdirectory(qmakearray)
add_subdirectory(qmap)
add_subdirectory(qmargins)
add_subdirectory(qmessageauthenticationcode)
add_subdirectory(qoffsetstringarray)
add_subdirectory(qpair)
add_subdirectory(qpoint)
add_subdirectory(qpointf)
add_subdirectory(qqueue)
add_subdirectory(qrect)
add_subdirectory(qringbuffer)
add_subdirectory(qscopedpointer)
add_subdirectory(qscopedvaluerollback)
add_subdirectory(qscopeguard)
add_subdirectory(qtaggedpointer)
add_subdirectory(qset)
add_subdirectory(qsharedpointer)
add_subdirectory(qsize)
add_subdirectory(qsizef)
add_subdirectory(qstl)
add_subdirectory(qtimeline)
add_subdirectory(qvarlengtharray)
add_subdirectory(qvector)
add_subdirectory(qversionnumber)
if(APPLE)
add_subdirectory(qmacautoreleasepool)
endif()

View File

@ -34,6 +34,7 @@ add_subdirectory(qringbuffer)
add_subdirectory(qscopedpointer)
add_subdirectory(qscopedvaluerollback)
add_subdirectory(qscopeguard)
add_subdirectory(qtaggedpointer)
add_subdirectory(qset)
# add_subdirectory(qsharedpointer) # special case not ported
add_subdirectory(qsize)

View File

@ -0,0 +1 @@
tst_qtaggedpointer

View File

@ -0,0 +1,12 @@
# Generated from qtaggedpointer.pro.
#####################################################################
## tst_qtaggedpointer Test:
#####################################################################
qt_add_test(tst_qtaggedpointer
SOURCES
tst_qtaggedpointer.cpp
PUBLIC_LIBRARIES
Qt::CorePrivate
)

View File

@ -0,0 +1,4 @@
CONFIG += testcase
TARGET = tst_qtaggedpointer
QT = core-private testlib
SOURCES = tst_qtaggedpointer.cpp

View File

@ -0,0 +1,400 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QtCore/qtaggedpointer.h>
class tst_QTaggedPointer : public QObject
{
Q_OBJECT
private Q_SLOTS:
void construction();
void dereferenceOperator();
void pointerOperator();
void negationOperator();
void operatorBool();
void comparison();
void tag();
void objectMember();
void customTagType();
};
void tst_QTaggedPointer::construction()
{
{
QTaggedPointer<int> p;
QCOMPARE(p.pointer(), nullptr);
QVERIFY(!p.tag());
}
{
QTaggedPointer<int> p(nullptr, 0x1);
QCOMPARE(p.pointer(), nullptr);
QCOMPARE(p.tag(), quintptr(0x1));
}
{
QScopedPointer<int> rawPointer(new int(5));
QTaggedPointer<int> p(rawPointer.data());
QCOMPARE(p.pointer(), rawPointer.data());
QVERIFY(!p.tag());
}
{
QScopedPointer<int> rawPointer(new int(5));
QTaggedPointer<int> p(rawPointer.data(), 0x1);
QCOMPARE(p.pointer(), rawPointer.data());
QCOMPARE(p.tag(), quintptr(0x1));
}
}
class AbstractClass
{
public:
virtual ~AbstractClass() {}
virtual int member() const = 0;
};
class SubClass : public AbstractClass
{
public:
int member() const { return 5; }
};
void tst_QTaggedPointer::dereferenceOperator()
{
/* Dereference a basic value. */
{
QScopedPointer<int> rawPointer(new int(5));
QTaggedPointer<int> p(rawPointer.data());
const int value = *p;
QCOMPARE(value, 5);
}
/* Dereference a basic value with tag. */
{
QScopedPointer<int> rawPointer(new int(5));
QTaggedPointer<int> p(rawPointer.data(), 0x1);
const int value = *p;
QCOMPARE(value, 5);
}
/* Dereference a pointer to an abstract class. This verifies
* that the operator returns a reference, when compiling
* with MSVC 2005. */
{
QScopedPointer<SubClass> ptr(new SubClass());
QTaggedPointer<AbstractClass> p(ptr.data());
QCOMPARE((*p).member(), 5);
}
/* The operator should be const. */
{
QScopedPointer<int> rawPointer(new int(5));
const QTaggedPointer<int> p(rawPointer.data());
*p;
}
/* A reference should be returned, not a value. */
{
QScopedPointer<int> rawPointer(new int(5));
const QTaggedPointer<int> p(rawPointer.data());
Q_UNUSED(static_cast<int &>(*p));
}
/* Instantiated on a const object, the returned object is a const reference. */
{
QScopedPointer<int> rawPointer(new int(5));
const QTaggedPointer<const int> p(rawPointer.data());
Q_UNUSED(static_cast<const int &>(*p));
}
}
class Value
{
public:
int value;
};
void tst_QTaggedPointer::pointerOperator()
{
{
QScopedPointer<Value> valuePtr(new Value{5});
QTaggedPointer<Value> p(valuePtr.data());
QCOMPARE(p->value, 5);
}
{
QScopedPointer<Value> valuePtr(new Value{5});
QTaggedPointer<Value> p(valuePtr.data(), 0x1);
QCOMPARE(p->value, 5);
}
/* The operator should be const. */
{
QScopedPointer<Value> valuePtr(new Value{5});
const QTaggedPointer<Value> p(valuePtr.data());
QVERIFY(p->value);
}
}
void tst_QTaggedPointer::negationOperator()
{
/* Invoke on default constructed value. */
{
QTaggedPointer<int> p;
QVERIFY(!p);
}
/* Invoke on nullptr value with tag. */
{
QTaggedPointer<int> p(nullptr, 0x1);
QVERIFY(!p);
}
/* Invoke on a value. */
{
QScopedPointer<int> rawPointer(new int(2));
QTaggedPointer<int> p(rawPointer.data());
QCOMPARE(!p, false);
}
/* Invoke on a value with tag. */
{
QScopedPointer<int> rawPointer(new int(2));
QTaggedPointer<int> p(rawPointer.data(), 0x1);
QCOMPARE(!p, false);
}
/* The signature should be const. */
{
const QTaggedPointer<int> p;
!p;
}
/* The return value should be bool. */
{
const QTaggedPointer<int> p;
Q_UNUSED(static_cast<bool>(!p));
}
}
void tst_QTaggedPointer::operatorBool()
{
/* Invoke on default constructed value. */
{
QTaggedPointer<int> p;
QCOMPARE(bool(p), false);
}
/* Invoke on nullptr value with tag. */
{
QTaggedPointer<int> p(nullptr, 0x1);
QCOMPARE(bool(p), false);
}
/* Invoke on active value. */
{
QScopedPointer<int> rawPointer(new int(3));
QTaggedPointer<int> p(rawPointer.data());
QVERIFY(p);
}
/* Invoke on active value with tag. */
{
QScopedPointer<int> rawPointer(new int(3));
QTaggedPointer<int> p(rawPointer.data(), 0x1);
QVERIFY(p);
}
/* The signature should be const and return bool. */
{
const QTaggedPointer<int> p;
(void)static_cast<bool>(p);
}
}
template <class A1, class A2, class B>
void comparisonTest(const A1 &a1, const A2 &a2, const B &b)
{
// test equality on equal pointers
QVERIFY(a1 == a2);
QVERIFY(a2 == a1);
// test inequality on equal pointers
QVERIFY(!(a1 != a2));
QVERIFY(!(a2 != a1));
// test equality on unequal pointers
QVERIFY(!(a1 == b));
QVERIFY(!(a2 == b));
QVERIFY(!(b == a1));
QVERIFY(!(b == a2));
// test inequality on unequal pointers
QVERIFY(b != a1);
QVERIFY(b != a2);
QVERIFY(a1 != b);
QVERIFY(a2 != b);
}
void tst_QTaggedPointer::comparison()
{
QScopedPointer<int> a(new int(5));
{
QTaggedPointer<int> a1(a.data());
QTaggedPointer<int> a2(a.data());
QScopedPointer<int> rawPointer(new int(6));
QTaggedPointer<int> b(rawPointer.data());
comparisonTest(a1, a1, b);
comparisonTest(a2, a2, b);
comparisonTest(a1, a2, b);
}
{
QTaggedPointer<int> a1(a.data(), 0x1);
QTaggedPointer<int> a2(a.data(), 0x1);
QScopedPointer<int> rawPointer(new int(6));
QTaggedPointer<int> b(rawPointer.data(), 0x1);
comparisonTest(a1, a1, b);
comparisonTest(a2, a2, b);
comparisonTest(a1, a2, b);
}
{
QTaggedPointer<int> a1(a.data(), 0x1);
QTaggedPointer<int> a2(a.data(), 0x2);
QScopedPointer<int> rawPointer(new int(6));
QTaggedPointer<int> b(rawPointer.data(), 0x2);
comparisonTest(a1, a1, b);
comparisonTest(a2, a2, b);
comparisonTest(a1, a2, b);
}
{
QTaggedPointer<int> p;
QVERIFY(p.isNull());
QVERIFY(p == nullptr);
QVERIFY(nullptr == p);
}
{
QTaggedPointer<int> p(nullptr, 0x1);
QVERIFY(p.isNull());
QVERIFY(p == nullptr);
QVERIFY(nullptr == p);
}
{
QScopedPointer<int> rawPointer(new int(42));
QTaggedPointer<int> p(rawPointer.data());
QVERIFY(!p.isNull());
QVERIFY(p != nullptr);
QVERIFY(nullptr != p);
}
{
QScopedPointer<int> rawPointer(new int(42));
QTaggedPointer<int> p(rawPointer.data(), 0x1);
QVERIFY(!p.isNull());
QVERIFY(p != nullptr);
QVERIFY(nullptr != p);
}
}
void tst_QTaggedPointer::tag()
{
QScopedPointer<int> rawPointer(new int(3));
QTaggedPointer<int> p(rawPointer.data());
QCOMPARE(*p.pointer(), 3);
QVERIFY(!p.tag());
p.setTag(0x1);
QCOMPARE(p.tag(), 0x1);
p.setTag(0x2);
QCOMPARE(p.tag(), 0x2);
}
struct Foo
{
Foo() : p(nullptr) {}
Foo(const Foo &other) : p(other.p) {}
Foo &operator=(const Foo &other) {
p = other.p;
return *this;
}
QTaggedPointer<int> p;
};
void tst_QTaggedPointer::objectMember()
{
QScopedPointer<int> rawPointer(new int(42));
Foo f;
f.p = QTaggedPointer<int>(rawPointer.data(), 0x1);
Foo f2(f);
QCOMPARE(f2.p.pointer(), f.p.pointer());
QCOMPARE(f2.p.tag(), f.p.tag());
Foo f3 = f;
QCOMPARE(f3.p.pointer(), f.p.pointer());
QCOMPARE(f3.p.tag(), f.p.tag());
}
class Bar
{
Q_GADGET
public:
enum Tag {
NoTag = 0,
FirstTag = 1,
SecondTag = 2
};
Q_DECLARE_FLAGS(Tags, Tag)
Q_FLAG(Tags)
int value;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(Bar::Tags)
void tst_QTaggedPointer::customTagType()
{
QScopedPointer<Bar> barPtr(new Bar{5});
typedef QTaggedPointer<Bar, Bar::Tags> TaggedBar;
TaggedBar p(barPtr.data());
QCOMPARE(p->value, 5);
QVERIFY(TaggedBar::maximumTag());
QVERIFY(!p.tag());
QCOMPARE(p.tag(), Bar::NoTag);
p.setTag(Bar::FirstTag | Bar::SecondTag);
QCOMPARE(p->value, 5);
QCOMPARE(p.tag(), Bar::FirstTag | Bar::SecondTag);
}
QTEST_MAIN(tst_QTaggedPointer)
#include "tst_qtaggedpointer.moc"

View File

@ -30,6 +30,7 @@ SUBDIRS=\
qscopedpointer \
qscopedvaluerollback \
qscopeguard \
qtaggedpointer \
qset \
qsharedpointer \
qsize \