Rework PointerHolder transition to make it smoother

* Don't surprise people with deprecation warnings
* Provide detailed instructions and support for the transition
This commit is contained in:
Jay Berkenbilt 2022-02-06 09:19:56 -05:00
parent df2f5c6a36
commit dd4f30226f
6 changed files with 575 additions and 291 deletions

View File

@ -47,11 +47,6 @@
includes so you don't have to try to include a header that won't
be there.
* PointerHolder: deprecate getPointer() and getRefcount(). If you
don't want to disable deprecation warnings in general but are not
ready to tackle this change yet, you can define
NO_POINTERHOLDER_DEPRECATION to suppress those.
* PointerHolder: add a get() method and a use_count() method for
compatibility with std::shared_ptr. In qpdf 11, qpdf's APIs will
switch to using std::shared_ptr instead of PointerHolder, though

86
TODO
View File

@ -388,35 +388,65 @@ Other notes:
PointerHolder to std::shared_ptr
================================
Remember to update the smart-pointers section of the manual in
design.rst.
To perform update:
Once all deprecation warnings are cleared up (changing getPointer() to
get() and getRefcount() to use_count()), the only real issues are that
implicit assignment of a pointer to a shared_ptr doesn't work while it
does for PointerHolder and containers are different. Using auto takes
care of containers.
Cherry-pick pointerholder branch commit
PointerHolder<X> x = new X() -->
auto x = std::make_shared<X>()
Upgrade just the library. This is not necessary, but it's an added
check that the compatibility code works since it will show that tests,
examples, and CLI will work properly with the upgraded APIs, which
provides some assurance that other people will have a smooth time with
their code.
PointerHolder<Base> x = new Derived() -->
auto x = std::shared_ptr<Base>(new Derived())
patrepl s/PointerHolder/std::shared_ptr/g {include,libqpdf}/qpdf/*.hh
patrepl s/PointerHolder/std::shared_ptr/g libqpdf/*.cc
patrepl s/make_pointer_holder/std::make_shared/g libqpdf/*.cc
patrepl s/make_array_pointer_holder/QUtil::make_shared_array/g libqpdf/*.cc
patrepl s,qpdf/std::shared_ptr,qpdf/PointerHolder, **/*.cc **/*.hh
git restore include/qpdf/PointerHolder.hh
cleanpatch
PointerHolder x(true, new T[5]) -->
auto x = std::shared_ptr(new T[5], std::default_delete<T[]>())
Increase to POINTERHOLDER_TRANSITION=3
X* x = new X(); PointerHolder<X> x_ph(x) -->
auto x_ph = std::make_shared<X>(); X* x = x_ph.get();
make build_libqpdf -- no errors
Derived* x = new Derived(); PointerHolder<Base> x_ph(x) -->
Derived* x = new Derived(); auto x_ph = std::shared_pointer<Base>(x);
Drop back to POINTERHOLDER_TRANSITION=2
Also remember
make check -- everything passes
auto x = std::shared_ptr(new T[5], std::default_delete<T[]>())
vs.
auto x = std::make_unique<T[]>(5)
Then upgrade everything else. It would work to just start here.
Increase to POINTERHOLDER_TRANSITION=3
patrepl s/PointerHolder/std::shared_ptr/g **/*.cc **/*.hh
patrepl s/make_pointer_holder/std::make_shared/g **/*.cc
patrepl s/make_array_pointer_holder/QUtil::make_shared_array/g **/*.cc
patrepl s,qpdf/std::shared_ptr,qpdf/PointerHolder, **/*.cc **/*.hh
git restore include/qpdf/PointerHolder.hh
git restore libtests/pointer_holder.cc
cleanpatch
Remove all references to PointerHolder.hh from everything except
public headers and pointer_holder.cc.
make check -- everything passes
Increase to POINTERHOLDER_TRANSITION=4
Do a clean build and make check -- everything passes
Final steps:
* Change to POINTERHOLDER_TRANSITION=4 in autoconf.mk.in.
* Check code formatting
* std::shared_ptr<Members> m can be replaced with
std::shared_ptr<Members> m_ph and Members* m if performance is critical
* Could try Members indirection with Members* for QPDFObjectHandle
When done:
* Update the smart-pointers section of the manual in design.rst
* Update comments in PointerHolder.hh
PointerHolder in public API:
@ -453,20 +483,6 @@ PointerHolder in public API:
QPDFPageObjectHelper::addContentTokenFilter(
PointerHolder<QPDFObjectHandle::TokenFilter>)
Strategy:
* Start with pointerholder branch
* Replace each public API one at a time
* Replace remaining internal uses; sometimes unique_ptr may be good,
particularly for temporary strings that are deleted in the same
scope and Members for non-copyable classes
* std::shared_ptr<Members> m can be replaced with
std::shared_ptr<Members> m_ph and Members* m if performance is critical
* Remove #include <PointerHolder.hh> from all headers
At that point, we're in a good state to make that compatibility
basically works. Then we can proceed to remove PointerHolder from
everything else.
ABI Changes
===========

View File

@ -22,13 +22,125 @@
#ifndef POINTERHOLDER_HH
#define POINTERHOLDER_HH
// In qpdf 11, PointerHolder will be derived from std::shared_ptr and
// will also include a fix to incorrect semantics of const
// PointerHolder objects. PointerHolder only allows a const
// PointerHolder to return a const pointer. This is wrong. Use a
// PointerHolder<const T> for that. A const PointerHolder should just
// not allow you to change what it points to. This is consistent with
// how regular pointers and standard library shared pointers work.
#ifndef POINTERHOLDER_TRANSITION
// In qpdf 11, #define POINTERHOLDER_IS_SHARED_POINTER
// In qpdf 11, issue a warning:
// #define POINTERHOLDER_TRANSITION 0 to suppress this warning, and see below.
// # warn "POINTERHOLDER_TRANSITION is not defined -- see qpdf/PointerHolder.hh"
// undefined = define as 0; will also issue a warning in qpdf 11
// 0 = no deprecation warnings
// 1 = make PointerHolder<T>(T*) explicit
// 2 = warn for use of getPointer() and getRefcount()
// 3 = warn for all use of PointerHolder
// 4 = don't define PointerHolder at all
# define POINTERHOLDER_TRANSITION 0
#endif // !defined(POINTERHOLDER_TRANSITION)
// *** WHAT IS HAPPENING ***
// In qpdf 11, PointerHolder will be replaced with std::shared_ptr
// wherever it appears in the qpdf API. The PointerHolder object will
// be derived from std::shared_ptr to provide a backward-compatible
// interface and will be mutually assignable with std::shared_ptr.
// Code that uses containers of PointerHolder will require adjustment.
// *** HOW TO TRANSITION ***
// The symbol POINTERHOLDER_TRANSITION can be defined to help you
// transition your code away from PointerHolder. You can define it
// before including any qpdf header files or including its definition
// in your build configuration. If not defined, it automatically gets
// defined to 0, which enables full backward compatibility. That way,
// you don't have to take action for your code to continue to work.
// If you want to work gradually to transition your code away from
// PointerHolder, you can define POINTERHOLDER_TRANSITION and fix the
// code so it compiles without warnings and works correctly. If you
// want to be able to continue to support old qpdf versions at the
// same time, you can write code like this:
// #ifndef POINTERHOLDER_TRANSITION
// ... use PointerHolder as before 10.6
// #else
// ... use PointerHolder or shared_ptr as needed
// #endif
// Each level of POINTERHOLDER_TRANSITION exposes differences between
// PointerHolder and std::shared_ptr. The easiest way to transition is
// to increase POINTERHOLDER_TRANSITION in steps of 1 so that you can
// test and handle changes incrementally.
// *** Transitions available starting at qpdf 10.6.0 ***
// POINTERHOLDER_TRANSITION = 1
//
// PointerHolder<T> has an implicit constructor that takes a T*, so
// you can replace a PointerHolder<T>'s pointer by directly assigning
// a T* to it or pass a T* to a function that expects a
// PointerHolder<T>. std::shared_ptr does not have this (risky)
// behavior. When POINTERHOLDER_TRANSITION = 1, PointerHolder<T>'s T*
// constructor is declared explicit. For compatibility with
// std::shared_ptr, you can still assign nullptr to a PointerHolder.
// Constructing all your PointerHolder<T> instances explicitly is
// backward compatible, so you can make this change without
// conditional compilation and still use the changes with older qpdf
// versions.
//
// Also defined is a make_pointer_holder method that acts like
// std::make_shared. You can use this as well, but it is not
// compatible with qpdf prior to 10.6. Like std::make_shared<T>,
// make_pointer_holder<T> can only be used when the constructor
// implied by its arguments is public. If you use this, you should be
// able to just replace it with std::make_shared when qpdf 11 is out.
// POINTERHOLDER_TRANSITION = 2
//
// std::shared_ptr as get() and use_count(). PointerHolder has
// getPointer() and getRefcount(). In 10.6.0, get() and use_count()
// were added as well. When POINTERHOLDER_TRANSITION = 2, getPointer()
// and getRefcount() are deprecated. Fix deprecation warnings by
// replacing with get() and use_count(). This breaks compatibility
// with qpdf older than 10.6. Search for CONST BEHAVIOR for an
// additional note.
//
// Once your code is clean at POINTERHOLDER_TRANSITION = 2, the only
// remaining issues that prevent simple replacement of PointerHolder
// with std::shared_ptr are shared arrays and containers, and neither
// of these are used in the qpdf API.
// *** Transitions available starting at qpdf 11.0.0 **
// NOTE: Until qpdf 11 is released, this is a plan and is subject to
// change. Be sure to check again after qpdf 11 is released.
// POINTERHOLDER_TRANSITION = 3
//
// Warn for all use of PointerHolder<T>. This help you remove all use
// of PointerHolder from your code and use std::shared_ptr instead.
// You will also have to transition any containers of PointerHolder in
// your code.
// POINTERHOLDER_TRANSITION = 4
//
// Suppress definition of the PointerHolder<T> type entirely.
// CONST BEHAVIOR
// PointerHolder<T> has had a long-standing bug in its const behavior.
// const PointerHolder<T>'s getPointer() method returns a T const*.
// This is incorrect and is not how regular pointers or standard
// library smart pointers behave. Making a PointerHolder<T> const
// should prevent reassignment of its pointer but not affect the thing
// it points to. For that, use PointerHolder<T const>. The new get()
// method behaves correctly in this respect and is therefore slightly
// different from getPointer(). This shouldn't break any correctly
// written code. If you are relying on the incorrect behavior, use
// PointerHolder<T const> instead.
// OLD DOCUMENTATION
// This class is basically std::shared_ptr but predates that by
// several years.
@ -58,6 +170,8 @@
// the underlying pointers provides a well-defined, if not
// particularly meaningful, ordering.
#include <cstddef>
template <class T>
class PointerHolder
{
@ -65,66 +179,78 @@ class PointerHolder
class Data
{
public:
Data(T* pointer, bool array) :
pointer(pointer),
array(array),
refcount(0)
{
}
~Data()
{
if (array)
{
delete [] this->pointer;
}
else
{
delete this->pointer;
}
}
T* pointer;
bool array;
int refcount;
Data(T* pointer, bool array) :
pointer(pointer),
array(array),
refcount(0)
{
}
~Data()
{
if (array)
{
delete [] this->pointer;
}
else
{
delete this->pointer;
}
}
T* pointer;
bool array;
int refcount;
private:
Data(Data const&) = delete;
Data& operator=(Data const&) = delete;
Data(Data const&) = delete;
Data& operator=(Data const&) = delete;
};
public:
#if POINTERHOLDER_TRANSITION >= 1
explicit
#endif // POINTERHOLDER_TRANSITION >= 1
PointerHolder(T* pointer = 0)
{
this->init(new Data(pointer, false));
}
{
this->init(new Data(pointer, false));
}
// Special constructor indicating to free memory with delete []
// instead of delete
PointerHolder(bool, T* pointer)
{
this->init(new Data(pointer, true));
}
{
this->init(new Data(pointer, true));
}
PointerHolder(PointerHolder const& rhs)
{
this->copy(rhs);
}
{
this->copy(rhs);
}
PointerHolder& operator=(PointerHolder const& rhs)
{
if (this != &rhs)
{
this->destroy();
this->copy(rhs);
}
return *this;
}
{
if (this != &rhs)
{
this->destroy();
this->copy(rhs);
}
return *this;
}
PointerHolder& operator=(decltype(nullptr))
{
this->operator=(PointerHolder<T>());
return *this;
}
~PointerHolder()
{
this->destroy();
}
{
this->destroy();
}
bool operator==(PointerHolder const& rhs) const
{
return this->data->pointer == rhs.data->pointer;
return this->data->pointer == rhs.data->pointer;
}
bool operator==(decltype(nullptr)) const
{
return this->data->pointer == nullptr;
}
bool operator<(PointerHolder const& rhs) const
{
return this->data->pointer < rhs.data->pointer;
return this->data->pointer < rhs.data->pointer;
}
// get() is for interface compatibility with std::shared_ptr
@ -135,33 +261,33 @@ class PointerHolder
// NOTE: The pointer returned by getPointer turns into a pumpkin
// when the last PointerHolder that contains it disappears.
#ifndef NO_POINTERHOLDER_DEPRECATION
#if POINTERHOLDER_TRANSITION >= 2
[[deprecated("use PointerHolder<T>::get() instead of getPointer()")]]
#endif
#endif // POINTERHOLDER_TRANSITION >= 2
T* getPointer()
{
return this->data->pointer;
}
#ifndef NO_POINTERHOLDER_DEPRECATION
{
return this->data->pointer;
}
#if POINTERHOLDER_TRANSITION >= 2
[[deprecated("use PointerHolder<T>::get() instead of getPointer()")]]
#endif
#endif // POINTERHOLDER_TRANSITION >= 2
T const* getPointer() const
{
return this->data->pointer;
}
#ifndef NO_POINTERHOLDER_DEPRECATION
{
return this->data->pointer;
}
#if POINTERHOLDER_TRANSITION >= 2
[[deprecated("use use_count() instead of getRefcount()")]]
#endif
#endif // POINTERHOLDER_TRANSITION >= 2
int getRefcount() const
{
return this->data->refcount;
}
{
return this->data->refcount;
}
// use_count() is for compatibility with std::shared_ptr
long use_count()
{
return static_cast<long>(this->data->refcount);
}
{
return static_cast<long>(this->data->refcount);
}
T const& operator*() const
{
@ -183,30 +309,44 @@ class PointerHolder
private:
void init(Data* data)
{
this->data = data;
++this->data->refcount;
}
{
this->data = data;
++this->data->refcount;
}
void copy(PointerHolder const& rhs)
{
this->init(rhs.data);
}
{
this->init(rhs.data);
}
void destroy()
{
bool gone = false;
{
if (--this->data->refcount == 0)
{
gone = true;
}
}
if (gone)
{
delete this->data;
}
}
{
bool gone = false;
{
if (--this->data->refcount == 0)
{
gone = true;
}
}
if (gone)
{
delete this->data;
}
}
Data* data;
};
template<typename T, typename... _Args>
inline PointerHolder<T>
make_pointer_holder(_Args&&... __args)
{
return PointerHolder<T>(new T(__args...));
}
template <typename T>
PointerHolder<T>
make_array_pointer_holder(size_t n)
{
return PointerHolder<T>(true, new T[n]);
}
#endif // POINTERHOLDER_HH

View File

@ -1,4 +1,8 @@
#define NO_POINTERHOLDER_DEPRECATION // we need to test the deprecated API
// We need to test the deprecated API
#ifdef POINTERHOLDER_TRANSITION
# undef POINTERHOLDER_TRANSITION
#endif
#define POINTERHOLDER_TRANSITION 0
#include <qpdf/PointerHolder.hh>
#include <iostream>

View File

@ -751,16 +751,26 @@ actually quite rare and largely avoidable.
Smart Pointers
--------------
This section describes changes to the use of smart pointers in qpdf in
versions 10.6.0 and 11.0.0.
This section describes changes to the use of smart pointers there were
made in qpdf 10.6.0 as well as some planned for 11.0.0.
Starting in qpdf 11, ``PointerHolder`` will be replaced with
``std::shared_ptr`` in qpdf's public API. A backward-compatible
``PointerHolder`` will be provided that should make it possible for
most code to remain unchanged. This new ``PointerHolder`` will be
marked deprecated but will provide a way to suppress the deprecation
warnings. Code that works with containers of ``PointerHolder`` may
have to be modified, though no qpdf interfaces do this.
``PointerHolder`` class will be provided that should make it possible
for most code to remain unchanged. ``PointerHolder`` may eventually be
removed from qpdf entirely, but this will not happen for a while to
make it easier for people who need to support multiple versions of
qpdf.
The ``POINTERHOLDER_TRANSITION`` preprocessor symbol has been
introduced to help people transition from ``PointerHolder`` to
``std::shared_ptr``. After qpdf 11 is released, to prepare for a
future qpdf without ``PointerHolder`` and to let them know that it is
no longer needed, a warning will be issued if
``<qpdf/PointerHolder.hh>`` is included, though it will be possible to
suppress the warning by defining ``POINTERHOLDER_TRANSITION``. In
10.6.0, there are some steps you can perform to prepare, but no action
is required.
The remainder of this section describes how to prepare if you want to
eliminate ``PointerHolder`` from your code or what to do if you want
@ -769,25 +779,31 @@ to stick with the old interfaces.
Changes in 10.6.0
~~~~~~~~~~~~~~~~~
In qpdf 10.6.0, two ``PointerHolder`` methods have been deprecated and
replaced with methods that are compatible with ``std::shared_ptr``:
In qpdf 10.6.0, the following changes have been made to
``PointerHolder`` to make its behavior closer to that of
``std::shared_ptr``:
- ``getPointer()`` -- use ``get()`` instead
- ``get()`` has been added as an alternative to ``getPointer()``
- ``getRefcount()`` -- use ``use_count()`` instead
- ``use_count()`` has been added as an alternative to ``getRefcount()``
If you build your code with deprecation warnings enabled and you want
to suppress these deprecation warnings for now, you can ``#define
NO_POINTERHOLDER_DEPRECATION`` before including any qpdf header files.
It may be possible to leave it this way long-term to facilitate
supporting older versions of qpdf without conditional compilation.
- A new global helper function ``make_pointer_holder`` behaves
similarly to ``std::make_shared``, so you can use
``make_pointer_holder<T>(args...)`` to create a ``PointerHolder<T>``
with ``new T(args...)`` as the pointer.
- A new global helper function ``make_array_pointer_holder`` takes a
size and creates a ``PointerHolder`` to an array. It is a
counterpart to the newly added ``QUtil::make_shared_array`` method,
which does the same thing with a ``std::shared_ptr``.
``PointerHolder`` has had a long-standing bug: a ``const
PointerHolder<T>`` would only provide a ``T const*`` with its
``getPointer`` method. This is incorrect and is not how standard C++
smart pointers or regular pointers behave. The correct semantics
would be that a ``const PointerHolder<T>`` would not accept a new
pointer after being created but would still allow you to modify the
``getPointer`` method. This is incorrect and is not how standard
library C++ smart pointers or regular pointers behave. The correct
semantics would be that a ``const PointerHolder<T>`` would not accept
a new pointer after being created (``PointerHolder`` has always
behaved correctly in this way) but would still allow you to modify the
item being pointed to. If you don't want to mutate the thing it points
to, use ``PointerHolder<T const>`` instead. The new ``get()`` method
behaves correctly. It is therefore not exactly the same as
@ -795,157 +811,290 @@ behaves correctly. It is therefore not exactly the same as
``std::shared_ptr``. This shouldn't make any difference to any
correctly written code.
Differences between ``PointerHolder`` and ``std::shared_ptr``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
How to Prepare
~~~~~~~~~~~~~~
Here is a list of things you need to think about when migrating from
``PointerHolder`` to ``std::shared_ptr``. After the list, we will
discuss how to address each one using the ``POINTERHOLDER_TRANSITION``
preprocessor symbol or other C++ coding techniques.
If you don't need to support versions of qpdf prior to 10.6, you can
just replace all occurrences of ``getPointer()`` with ``get()`` and
all occurrences of ``getRefcount()`` with ``use_count()``. That's
about all you will be able to do prior to qpdf 11.
- ``PointerHolder<T>`` has an *implicit* constructor that takes a
``T*``, which means you can assign a ``T*`` directly to a
``PointerHolder<T>`` or pass a ``T*`` to a function that expects a
``PointerHolder<T>`` as a parameter. ``std::shared_ptr<T>`` does not
have this behavior, though you can still assign ``nullptr`` to a
``std::shared_ptr<T>`` and compare ``nullptr`` with a
``std::shared_ptr<T>``. Here are some examples of how you might need
to change your code:
If you need to support older versions, you have two choices:
Old code:
.. code-block:: c++
- ``#define NO_POINTERHOLDER_DEPRECATION`` and leave everything the
way it was. You can just wait until qpdf 11.
PointerHolder<X> x_p;
X* x = new X();
x_p = x;
- Write code that uses ``get()`` but falls back to ``getPointer()`` if
``QPDF_MAJOR_VERSION`` is not defined. The symbols
``QPDF_MAJOR_VERSION``, ``QPDF_MINOR_VERSION``, and
``QPDF_PATCH_VERSION`` were introduced with 10.6.0, so just checking
for whether ``QPDF_MAJOR_VERSION`` is defined is sufficient for
telling if you're running a version before 10.6.0. If you do this,
once qpdf 11 comes out, you will already know all the places that
have to be handled specially.
New code:
.. code-block:: c++
If you are somehow relying on the fact that a ``const
PointerHolder<T>`` always gave back a ``T const*`` and are
dereferencing a ``const PointerHolder<T>`` to call methods that only
have ``const`` versions in ``T``, you may have to change from
``const PointerHolder<T>`` to ``PointerHolder<T const>``. This won't
be an issue for anything in the qpdf API, and if you are using qpdf
``PointerHolder`` objects for any other reason, you should just
replace them with ``std::shared_ptr``.
auto x_p = std::make_shared<X>();
X* x = x_p.get();
// or, less safe, but closer:
std::shared_ptr<X> x_p;
X* x = new X();
x_p = std::shared_ptr<X>(x);
What to Expect
~~~~~~~~~~~~~~
Old code:
.. code-block:: c++
Note: if you are reading this in the 10.6 manual and 11 is out, you
should read it in the manual for qpdf 11 instead. Some early tests
have been done to try to ensure the accuracy of this information, but
it may change once the work is actually completed.
PointerHolder<Base> base_p;
Derived* derived = new Derived();
base_p = derived;
When ``PointerHolder`` disappears from qpdf's API in qpdf 11, you will
have a few options:
New code:
.. code-block:: c++
- Use the new ``PointerHolder``, which is derived from
``std::shared_ptr`` and which has methods to make it
interchangeable. For things that use ``PointerHolder<T>`` directly,
this should "just work," though you will have to ``#define
NO_POINTERHOLDER_DEPRECATION`` if you don't want deprecation
warnings.
std::shared_ptr<Base> base_p;
Derived* derived = new Derived();
base_p = std::shared_ptr<Base>(derived);
- Replace all uses of ``PointerHolder<T>`` with ``std::shared_ptr<T>``
and deal with the required changes, outlined below. This is the
recommended course of action. You will need conditional compilation
if you want to simultaneously support order code. Stay tuned for the
qpdf 11 documentation for specifics.
- ``PointerHolder<T>`` has ``getPointer()`` to get the underlying
pointer. It also has the seldom-used ``getRefcount()`` method to get
the reference count. ``std::shared_ptr<T>`` has ``get()`` and
``use_count()``. In qpdf 10.6, ``PointerHolder<T>`` also has
would not be an issue unless you did this in your own code.
While ``PointerHolder<T>`` and ``std::shared_ptr<T>`` will be mutually
assignable and convertible, this does not apply to containers of those
objects. The qpdf API doesn't have any containers of
``PointerHolder``, so this would have to be in your own code. You can
prepare yourself for the change by using ``auto`` and ``decltype``
whenever possible so that a change to the underlying type of something
won't require source changes.
Addressing the Differences
~~~~~~~~~~~~~~~~~~~~~~~~~~
Required Changes in qpdf 11
~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you need to support versions of qpdf prior to qpdf 10.6, you don't
*need* to take any action at this time, but it is recommended that you
at least address the implicit constructor issue since this can be done
without breaking backward compatibility. (Explicit construction of
``PointerHolder<T>`` is and always has been allowed.)
This section describes unavoidable changes when replacing
``PointerHolder`` with ``std::shared_ptr`` rather than continuing to
use the backward compatible API. Nothing here is needed (or can be
done) prior to qpdf 11, so consider this to be a preview.
There are two significant things you can do to minimize the impact of
switching from ``PointerHolder`` to ``std::shared_ptr``:
- Change ``getPointer`` to ``get`` and ``getRefcount`` to
``use_count`` as above. If your starting point is no deprecation
warnings with qpdf 10.6, this will already be true.
- Use ``auto`` and ``decltype`` whenever possible when working with
``PointerHolder`` variables that are exchanged with the qpdf API.
- Array allocations will have to be rewritten.
- Use the ``POINTERHOLDER_TRANSITION`` preprocessor symbol to identify
and resolve the differences described above.
To allocate a ``PointerHolder`` to an array:
To use ``POINTERHOLDER_TRANSITION``, you will need to ``#define`` it
before including any qpdf header files or specify its value as part of
your build. The table below describes the values of
``POINTERHOLDER_TRANSITION``. This informatoin is also summarized in
:file:`include/qpdf/PointerHolder.hh`, so you will have it handy
without consulting this manual.
.. code-block:: c++
.. list-table:: POINTERHOLDER_TRANSITION values
:widths: 5 80
:header-rows: 1
PointerHolder<X> p(true, new X[n]);
- - value
- meaning
To allocate a ``std::shared_ptr`` to an array:
- - undefined
- same as ``0``, but start with qpdf 11.0, issues a warning
.. code-block:: c++
- - ``0``
- provide a backward compatible ``PointerHolder`` and suppress
all deprecation warnings
auto p = std::shared_ptr<X>(new X[n], std::default_delete<X[]>());
- - ``1``
- Make the ``PointerHolder<T>(T*)`` constructor explicit
To allocate a ``std::unique_ptr`` to an array:
- - ``2``
- Deprecate ``getPointer()`` and ``getRefcount()``
.. code-block:: c++
- - ``3``
- Starting in qpdf 11, deprecate all uses of ``PointerHolder``
auto p = std::make_unique<X[]>(n);
// or
auto p = std::unique_ptr<X[]>(new X[n]);
- - ``4``
- Starting in qpdf 11, disable all functionality from
``qpdf/PointerHolder.hh`` so that ``#include``-ing it has no
effect.
The second form may be needed if ``X`` has a private constructor
from this context.
Based on the above, here is a procedure for preparing your code. This
is the procedure that was used for the qpdf code itself.
C++-17 has a better way to allocate ``std::shared_ptr`` to an array,
but qpdf is still allowing C++-14 to be used. You can use whatever
method to handle shared arrays that is supported in your
environment. There are no shared arrays in qpdf's public API except
for some ``QUtil`` helper methods that are not essential for use of
qpdf features.
If you need to support versions of qpdf prior to 10.6, you can still
do these steps:
- ``PointerHolder<T>`` can have plain pointers directly assigned to
it, while ``std::shared_ptr<T>`` cannot. This makes code like this
possible:
- Find all occurrences of ``PointerHolder`` in the code. See whether
any of them can just be outright replaced with ``std::shared_ptr``
or ``std::unique_ptr``. If you have been using qpdf prior to
adopting C++11 and were using ``PointerHolder`` as a general-purpose
smart pointer, you may have cases that can be replaced in this way.
.. code-block:: c++
For example:
PointerHolder<X> x_p;
X* x = new X();
x_p = x;
- Simple ``PointerHolder<T>`` construction can be replaced with
either the equivalent ``std::shared_ptr<T>`` construction or, if
the constructor is public, with ``std::make_shared<T>(args...)``.
If you are creating a smart pointer that is never copied, you may
be able to use ``std::unique_ptr<T>`` instead.
It also makes it possible to pass a plain pointer to a function
expecting a ``PointerHolder``, thereby transferring "ownership" of
the pointer into the function.
- Array allocations will have to be rewritten.
Code like that is a risky because you can leak memory if an
exception is thrown between creation of the X and assignment of it
into the ``PointerHolder``. In any case, ``std::shared_ptr`` does
not allow that, so you need one of these instead:
Allocating a ``PointerHolder`` to an array looked like this:
.. code-block:: c++
.. code-block:: c++
auto x_p = std::make_shared<X>();
X* x = x_p.get();
// or, less safe, but closer:
std::shared_ptr<X> x_p;
X* x = new X();
x_p = std::shared_ptr<X>(x);
PointerHolder<X> p(true, new X[n]);
Also, code like this:
To allocate a ``std::shared_ptr`` to an array:
.. code-block:: c++
.. code-block:: c++
PointerHolder<Base> base_p;
Derived* derived = new Derived();
base_p = derived;
auto p = std::shared_ptr<X>(new X[n], std::default_delete<X[]>());
// If you don't mind using QUtil, there's QUtil::make_shared_array<X>(n).
// If you are using c++20, you can use std::make_shared<X[]>(n)
// to get a std::shared_ptr<X[]> instead of a std::shared_ptr<X>.
needs to be replaced with something like this instead:
To allocate a ``std::unique_ptr`` to an array:
.. code-block:: c++
.. code-block:: c++
std::shared_ptr<Base> base_p;
Derived* derived = new Derived();
base_p = std::shared_ptr<Base>(derived);
auto p = std::make_unique<X[]>(n);
// or, if X has a private constructor:
auto p = std::unique_ptr<X[]>(new X[n]);
- If a ``PointerHolder<T>`` can't be replaced with a standard library
smart pointer, perhaps it can be declared using ``auto`` or
``decltype`` so that, when the qpdf API changes, your code will just
need to be recompiled.
- ``#define POINTERHOLDER_TRANSITION 1`` to enable deprecation
warnings for all implicit constructions of ``PointerHolder<T>`` from
a plain ``T*``. When you find one, explicitly construct the
``PointerHolder<T>``.
- Old code:
.. code-block:: c++
PointerHolder<X> x = new X();
- New code:
.. code-block:: c++
auto x = PointerHolder<X>(new X(...)); // all versions of qpdf
// or, if X(...) is public:
auto x = make_pointer_holder<X>(...); // only 10.6 and above
Other examples appear above.
If you need to support older versions of qpdf than 10.6, this is as
far as you can go until qpdf 11 comes out.
If you only need to support the latest version of qpdf, proceed as
follows:
- ``#define POINTERHOLDER_TRANSITION 2`` to enable deprecation of
``getPointer()`` and ``getRefcount()``
- Replace ``getPointer()`` with ``get()`` and ``getRefcount()`` with
``use_count()``. These methods were not present prior to 10.6.0.
When you have gotten your code to compile cleanly with
``POINTERHOLDER_TRANSITION=2``, you are well on your way to being
ready for eliminating ``PointerHolder`` entirely after qpdf 11 is
released.
After qpdf 11 is out
~~~~~~~~~~~~~~~~~~~~
In the 10.6 manual, this section represents a plan and is subject to
change. However, it has been tested in practice using a version of the
qpdf 11 ``PointerHolder`` on a branch, so it is likely to be accurate.
In the meantime, think of this is a preview.
First, make sure you have done the steps in the 10.6 section. (Note:
once qpdf 11 comes out, the goal is to not have to migrate to 10.6
first, so it is likely that these sections will be combined.)
If you are explicitly choosing to stick with the backward compatible
``PointerHolder`` for now, you should define
``POINTERHOLDER_TRANSITION`` to ``0`` to suppress the warning from
including ``qpdf/PointerHolder.hh``. Be aware that you may eventually
have to deal with the transition, though the intention is to leave the
compatibility layer in place for a while. You should rebuild and test
your code. There may be compiler errors if you have containers of
``PointerHolder``, but most code should compile without any changes.
Even if you have errors, use of ``auto`` or ``decltype`` may enable
you to write code that works with the old and new API without having
to use conditional compilation. The
``POINTERHOLDER_IS_SHARED_POINTER`` is defined in qpdf 11 if you
``#include <qpdf/PointerHolder.hh>``.
If you want to support older versions of qpdf and still transition so
that the backward-compatible ``PointerHolder`` is not in use, you can
separate old code and new code by testing with the
``POINTERHOLDER_IS_SHARED_POINTER`` preprocessor symbol, as in
.. code-block:: c++
#ifdef POINTERHOLDER_IS_SHARED_POINTER
std::shared_ptr<X> x;
#else
PointerHolder<X> x;
#endif // POINTERHOLDER_IS_SHARED_POINTER
x = decltype(x)(new X())
or
.. code-block:: c++
#ifdef POINTERHOLDER_IS_SHARED_POINTER
auto x_p = std::make_shared<X>();
X* x = x_p.get();
#else
auto x_p = PointerHolder<X>(new X());
X* x = x_p.getPointer();
#endif // POINTERHOLDER_IS_SHARED_POINTER
x_p->doSomething();
x->doSomethingElse();
If you don't need to support older versions of qpdf, you can proceed
with these steps without protecting changes with the preprocessor
symbol. Here are the remaining changes.
- Make sure you have a clean build with ``POINTERHOLDER_TRANSITION``
set to ``2``. This means that you are using ``PointerHolder`` in a
manner that is API-compatible with ``std::shared_ptr`` in all cases
except for array pointers.
- Replace all occurrences of ``PointerHolder`` with
``std::shared_ptr`` except in ``#include <qpdf/PointerHolder.hh>``
- Replace all occurrences of ``make_pointer_holder`` with
``std::make_shared``
- Replace all occurrences of ``make_array_pointer_holder`` with
``QUtil::make_shared_array``. You will need to include
``<qpdf/QUtil.hh>`` if you haven't already done so.
- Make sure ``<memory>`` is included wherever you were including
``<qpdf/PointerHolder.hh>``.
- If you were using any array ``PointerHolder<T>`` objects, replace
them as above. You can let the compiler find these for you.
- ``#define POINTERHOLDER_TRANSITION 3`` to enable deprecation of
all ``PointerHolder<T>`` construction.
- Build and test. Fix any remaining issues.
- If not supporting older qpdf, remove all references to
``<qpdf/PointerHolder.hh>``. Otherwise, you wil still need to
include it but can ``#define POINTERHOLDER_TRANSITION 4`` to prevent
``PointerHolder`` from being defined. The
``POINTERHOLDER_IS_SHARED_POINTER`` symbol will still be defined.
Historical Background
~~~~~~~~~~~~~~~~~~~~~
@ -953,13 +1102,6 @@ Historical Background
Since its inception, the qpdf library used its own smart pointer
class, ``PointerHolder``. The ``PointerHolder`` class was originally
created long before ``std::shared_ptr`` existed, and qpdf itself
didn't start requiring a C++-11 compiler version 9.1.0 released in
late 2019.
``PointerHolder`` is a reference-counted smart pointer with semantics
almost identical to ``std::shared_ptr`` except that it is not
thread-safe. It has a few interface differences that prevent
``std::shared_ptr`` from being a drop-in replacement. However, given
the value of using standard library smart pointers, qpdf is taking the
plunge for version 11 and switching over to standard library smart
pointers.
didn't start requiring a C++11 compiler version 9.1.0 released in
late 2019. With current C++ versions, is no longer desirable for qpdf
to have its own smart pointer class.

View File

@ -7,28 +7,15 @@ For a detailed list of changes, please see the file
:file:`ChangeLog` in the source distribution.
10.6.0: XXX
- Deprecations/future replacement of ``PointerHolder``
- Preparation for replacement of ``PointerHolder``
The next major release of qpdf will replace ``PointerHolder`` with
``std::shared_ptr`` across all of qpdf's public API. In
preparation for this change, the following ``PointerHolder``
methods have been deprecated in favor of interfaces that more
closely match ``std::shared_ptr``:
- ``getPointer()`` -- use ``get()`` instead; this also fixes
``const`` semantics as discussed in
:file:`include/qpdf/PointerHolder.hh`.
- ``getRefcount()`` -- use ``use_count()`` instead
If you build your code with deprecation warnings enabled and you
want to suppress these deprecation warnings for now, you can
``#define NO_POINTERHOLDER_DEPRECATION`` before including any qpdf
header files. Code that does this will *require no changes* prior
to qpdf 11 and may or may not require changes after qpdf 11.
For a detailed discussion of this change and how to prepare for
it, see :ref:`smart-pointers`.
``std::shared_ptr`` across all of qpdf's public API. No action is
required at this time, but if you'd like to prepare, read the
comments :file:`include/qpdf/PointerHolder.hh` and see
:ref:`smart-pointers` for details on what you can do now to create
code that will continue to work with older versions of qpdf and be
easier to switch over to qpdf 11 when it comes out.
- Preparation for a new JSON output version