604. [dec.tr] Storing a reference to a facet unsafe.

Section: 99 [dec.tr::trdec.types] Status: TRDec Submitter: Martin Sebor Opened: 2006-05-28 Last modified: 2016-02-10

Priority: Not Prioritized

View all other issues in [dec.tr::trdec.types].

View all issues with TRDec status.

Discussion:

In c++std-lib-17197, Martin writes:

The extended_num_get and extended_num_put facets are designed to store a reference to a num_get or num_put facet which the extended facets delegate the parsing and formatting of types other than decimal. One form of the extended facet's ctor (the default ctor and the size_t overload) obtains the reference from the global C++ locale while the other form takes this reference as an argument.

The problem with storing a reference to a facet in another object (as opposed to storing the locale object in which the facet is installed) is that doing so bypasses the reference counting mechanism designed to prevent a facet that is still being referenced (i.e., one that is still installed in some locale) from being destroyed when another locale that contains it is destroyed. Separating a facet reference from the locale it comes from van make it cumbersome (and in some cases might even make it impossible) for programs to prevent invalidating the reference. (The danger of this design is highlighted in the paper.)

This problem could be easily avoided by having the extended facets store a copy of the locale from which they would extract the base facet either at construction time or when needed. To make it possible, the forms of ctors of the extended facets that take a reference to the base facet would need to be changed to take a locale argument instead.

Proposed resolution:

1. Change the extended_num_get synopsis in 3.10.2 as follows:

            extended_num_get(const std::num_get<charT, InputIterator> std::locale & b, size_t refs = 0);

            /* ... */

            // const std::num_get<charT, InputIterator> & base;        exposition only
            // std::locale baseloc;                                    exposition only

2. Change the description of the above constructor in 3.10.2.1:

            extended_num_get(const std::num_get<charT, InputIterator> std::locale & b, size_t refs = 0);

Effects: Constructs an extended_num_get facet as if by:

       extended_num_get(const std::num_get<charT, InputIterator> std::locale & b, size_t refs = 0)
                : facet(refs), baseloc(b)
                { /* ... */ }

Notes: Care must be taken by the implementation to ensure that the lifetime of the facet referenced by base exceeds that of the resulting extended_num_get facet.

3. Change the Returns: clause for do_get(iter_type, iter_type, ios_base &, ios_base::iostate &, bool &) const, et al to

Returns: base std::use_facet<std::num_get<charT, InputIterator> >(baseloc).get(in, end, str, err, val).

4. Change the extended_num_put synopsis in 3.10.3 as follows:

            extended_num_put(const std::num_put<charT, OutputIterator> std::locale & b, size_t refs = 0);

            /* ... */

            // const std::num_put<charT, OutputIterator> & base;       exposition only
            // std::locale baseloc;                                    exposition only

5. Change the description of the above constructor in 3.10.3.1:

            extended_num_put(const std::num_put<charT, OutputIterator> std::locale & b, size_t refs = 0);

Effects: Constructs an extended_num_put facet as if by:

       extended_num_put(const std::num_put<charT, OutputIterator> std::locale & b, size_t refs = 0)
                : facet(refs), baseloc(b)
                { /* ... */ }

Notes: Care must be taken by the implementation to ensure that the lifetime of the facet referenced by base exceeds that of the resulting extended_num_put facet.

6. Change the Returns: clause for do_put(iter_type, ios_base &, char_type, bool &) const, et al to

Returns: base std::use_facet<std::num_put<charT, OutputIterator> >(baseloc).put(s, f, fill, val).

[ Redmond: We would prefer to rename "extended" to "decimal". ]