This question came up as I answered this question: does the standard allow and make any guarantees about friend
-ing standard library classes and/or functions?
In this particular case, the situation the question was whether:
class MyUserDefinedType
{
friend struct std::default_delete<MyUserDefinedType>;
private:
~MyUserDefinedType() { }
}
is guaranteed to allow MyUserDefinedType
to be stored in a std::unique_ptr<MyUserDefinedType>
or std::shared_ptr<MyUserDefinedType>
object with the default deleter.
In general, are classes described in the standard library required to implement their functionality directly, or can they use any arbitrary level of indirection? For example, is it possible that
std::default_delete<MyUserDefinedType>
is actually a using
alias of a class defined in an inner namespace of std
, in which case the friend
declaration would be illegalor
std::default_delete<MyUserDefinedType>
calls some other class that actually does the deleting, in which case the friend
declaration would not have the desired effector something else along those lines?
My guess is that this is UB not guaranteed to work but I am curious if this is addressed specifically by the standard.
This specific example given above works for clang trunk (w/libc++) and GCC 4.7.2 (w/libstdc++), FWIW
is it possible that
std::default_delete<MyUserDefinedType>
is actually a using alias of a class defined in an inner namespace of std, in which case the friend declaration would be illegal?
No. Per Paragraph 20.7.1.1.2 of the C++11 Standard:
namespace std {
template <class T> struct default_delete {
constexpr default_delete() noexcept = default;
template <class U> default_delete(const default_delete<U>&) noexcept;
void operator()(T*) const;
};
}
The fact that it has to be a class template is explicitly specified. This means it cannot be an alias template. If that was the case, it would also be impossible to specialize it.
is it possible that
std::default_delete<MyUserDefinedType>
calls some other class that actually does the deleting, in which case the friend declaration would not have the desired effect?
Yes. Nothing in the Standard specifies that the call cannot be done by some internal helper. Per Paragraph 20.1.1.2:
void operator()(T *ptr) const;
3 Effects: calls
delete
onptr
.4 Remarks: If
T
is an incomplete type, the program is ill-formed.
This only specifies what the effect of invoking the call operator on the default_delete<>
functor should be, not how this shall be achieved concretely (whether directly inside the body of the call operator, or by delegating the task to some member function of some other class).
In general, are classes described in the standard library required to implement their functionality directly, or can they use any arbitrary level of indirection?
In general an implementation may indirect as much as it wants. Have e.g. a look at the implementations of standard containers and their iterators - or just use them wrong and see which templates are involved from the error messages. However, since default_delete
is nothing magic, that should be a one-liner, you can expect it to do the work itself, but it's not guaranteed.
My guess is that this is UB but I am curious if this is addressed specifically by the standard.
It's not UB, it's just unspecified.
You could be sure if you just specialized default_delete<MyUserDefinedType>
(it is allowed to specialize standard libraray templates), but I would not do that.
I would not use friendship at all, especially not if it comes to templates that have not been specialized. Consider this:
//your code
class MyUserDefinedType
{
friend struct std::default_delete<MyUserDefinedType>; //for deletion
private:
int veryPrivateData;
~MyUserDefinedType() { }
};
//evil colleague's code:
namespace std {
//we may specialize std-templates for UDTs...
template<>
struct default_delete<MyUserDefinedType>
{
constexpr default_delete() noexcept = default;
template <class U> default_delete(const default_delete<U>&) noexcept {}
void operator()(T* pt) const { delete pt; }
//sneaky...
void access(MyUserDefinedType& mudt, int i) const
{ mudt.veryPrivateData = i; }
};
}
void somewhere_deep_in_the_code()
{
MyUserDefinedType& myUDT = /*something...*/;
std::default_delete<MyUserDefinedType>().access(myUDT, 42); //tricked you!
}
Friends can do anything to you. Choose them with caution. In this case, I'd really recommend a custom deleter - assuming it makes any sense making the destructor private but providing access to it via the deleter.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With