diff options
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | TODO | 86 | ||||
-rw-r--r-- | include/qpdf/PointerHolder.hh | 318 | ||||
-rw-r--r-- | libtests/pointer_holder.cc | 6 | ||||
-rw-r--r-- | manual/design.rst | 424 | ||||
-rw-r--r-- | manual/release-notes.rst | 27 |
6 files changed, 575 insertions, 291 deletions
@@ -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 @@ -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 =========== diff --git a/include/qpdf/PointerHolder.hh b/include/qpdf/PointerHolder.hh index 8e758ee6..2297ba21 100644 --- a/include/qpdf/PointerHolder.hh +++ b/include/qpdf/PointerHolder.hh @@ -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 diff --git a/libtests/pointer_holder.cc b/libtests/pointer_holder.cc index 87c1fa07..3db2241a 100644 --- a/libtests/pointer_holder.cc +++ b/libtests/pointer_holder.cc @@ -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> diff --git a/manual/design.rst b/manual/design.rst index aeec31a9..42def1bf 100644 --- a/manual/design.rst +++ b/manual/design.rst @@ -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`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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. + +- ``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: + + Old code: + .. code-block:: c++ + + PointerHolder<X> x_p; + X* x = new X(); + x_p = x; + + New code: + .. 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); + + Old code: + .. code-block:: c++ + + PointerHolder<Base> base_p; + Derived* derived = new Derived(); + base_p = derived; + + New code: + .. code-block:: c++ + + std::shared_ptr<Base> base_p; + Derived* derived = new Derived(); + base_p = std::shared_ptr<Base>(derived); + +- ``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. + +Addressing the Differences +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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.) + +There are two significant things you can do to minimize the impact of +switching from ``PointerHolder`` to ``std::shared_ptr``: + +- Use ``auto`` and ``decltype`` whenever possible when working with + ``PointerHolder`` variables that are exchanged with the qpdf API. + +- Use the ``POINTERHOLDER_TRANSITION`` preprocessor symbol to identify + and resolve the differences described above. + +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. + +.. list-table:: POINTERHOLDER_TRANSITION values + :widths: 5 80 + :header-rows: 1 + + - - value + - meaning + + - - undefined + - same as ``0``, but start with qpdf 11.0, issues a warning + + - - ``0`` + - provide a backward compatible ``PointerHolder`` and suppress + all deprecation warnings + + - - ``1`` + - Make the ``PointerHolder<T>(T*)`` constructor explicit + + - - ``2`` + - Deprecate ``getPointer()`` and ``getRefcount()`` + + - - ``3`` + - Starting in qpdf 11, deprecate all uses of ``PointerHolder`` + + - - ``4`` + - Starting in qpdf 11, disable all functionality from + ``qpdf/PointerHolder.hh`` so that ``#include``-ing it has no + effect. + +Based on the above, here is a procedure for preparing your code. This +is the procedure that was used for the qpdf code itself. + +If you need to support versions of qpdf prior to 10.6, you can still +do these steps: + +- 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. + + For example: + + - 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. + + - Array allocations will have to be rewritten. -How to Prepare -~~~~~~~~~~~~~~ + Allocating a ``PointerHolder`` to an array looked like this: -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. + .. code-block:: c++ -If you need to support older versions, you have two choices: + PointerHolder<X> p(true, new X[n]); -- ``#define NO_POINTERHOLDER_DEPRECATION`` and leave everything the - way it was. You can just wait until qpdf 11. + To allocate a ``std::shared_ptr`` to an array: -- 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. + .. 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 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>. -What to Expect -~~~~~~~~~~~~~~ + To allocate a ``std::unique_ptr`` to an array: -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. + .. code-block:: c++ -When ``PointerHolder`` disappears from qpdf's API in qpdf 11, you will -have a few options: + auto p = std::make_unique<X[]>(n); + // or, if X has a private constructor: + auto p = std::unique_ptr<X[]>(new X[n]); -- 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. +- 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. -- 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. +- ``#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>``. -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. + - Old code: -Required Changes in qpdf 11 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ + .. code-block:: c++ -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. + PointerHolder<X> x = new X(); -- 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. + - New code: -- Array allocations will have to be rewritten. + .. code-block:: c++ - To allocate a ``PointerHolder`` to an array: + 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 - .. code-block:: c++ + Other examples appear above. - PointerHolder<X> p(true, new X[n]); +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. - To allocate a ``std::shared_ptr`` to an array: +If you only need to support the latest version of qpdf, proceed as +follows: - .. code-block:: c++ +- ``#define POINTERHOLDER_TRANSITION 2`` to enable deprecation of + ``getPointer()`` and ``getRefcount()`` - auto p = std::shared_ptr<X>(new X[n], std::default_delete<X[]>()); +- Replace ``getPointer()`` with ``get()`` and ``getRefcount()`` with + ``use_count()``. These methods were not present prior to 10.6.0. - To allocate a ``std::unique_ptr`` to an array: +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. - .. code-block:: c++ +After qpdf 11 is out +~~~~~~~~~~~~~~~~~~~~ - auto p = std::make_unique<X[]>(n); - // or - auto p = std::unique_ptr<X[]>(new X[n]); +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. - The second form may be needed if ``X`` has a private constructor - from this context. +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.) - 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 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>``. -- ``PointerHolder<T>`` can have plain pointers directly assigned to - it, while ``std::shared_ptr<T>`` cannot. This makes code like this - possible: +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++ +.. code-block:: c++ - PointerHolder<X> x_p; - X* x = new X(); - x_p = x; + #ifdef POINTERHOLDER_IS_SHARED_POINTER + std::shared_ptr<X> x; + #else + PointerHolder<X> x; + #endif // POINTERHOLDER_IS_SHARED_POINTER + x = decltype(x)(new X()) - 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. +or - 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: +.. code-block:: c++ - .. 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(); - 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); +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. - Also, code like this: +- 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. - .. code-block:: c++ +- Replace all occurrences of ``PointerHolder`` with + ``std::shared_ptr`` except in ``#include <qpdf/PointerHolder.hh>`` - PointerHolder<Base> base_p; - Derived* derived = new Derived(); - base_p = derived; +- Replace all occurrences of ``make_pointer_holder`` with + ``std::make_shared`` - needs to be replaced with something like this instead: +- 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. - .. code-block:: c++ +- Make sure ``<memory>`` is included wherever you were including + ``<qpdf/PointerHolder.hh>``. - std::shared_ptr<Base> base_p; - Derived* derived = new Derived(); - base_p = std::shared_ptr<Base>(derived); +- 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. diff --git a/manual/release-notes.rst b/manual/release-notes.rst index 19e172e8..d738e534 100644 --- a/manual/release-notes.rst +++ b/manual/release-notes.rst @@ -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 |