在std命名空间中定义的朋友类:任何保证?

时间:2013-03-04 06:31:38

标签: c++ std language-lawyer

当我回答this question时,出现了这个问题:标准是否允许并对friend标准库类和/或函数做出任何保证?

在这种特殊情况下,问题的关键是:

class MyUserDefinedType
{
    friend struct std::default_delete<MyUserDefinedType>;

private:
    ~MyUserDefinedType() { }
}

保证允许MyUserDefinedType使用默认删除器存储在std::unique_ptr<MyUserDefinedType>std::shared_ptr<MyUserDefinedType>对象中。

通常,标准库中描述的类是直接实现其功能所必需的,还是可以使用任意级别的间接?例如,是否有可能

  • std::default_delete<MyUserDefinedType>实际上是using内部命名空间中定义的类的std别名,在这种情况下friend声明是非法的

  • std::default_delete<MyUserDefinedType>调用其他实际执行删除的类,在这种情况下,friend声明不会产生预期效果

或其他类似的东西?

我的猜测是 UB 无法保证可以正常工作,但我很好奇这是否是标准专门解决的问题。

上面给出的这个具体例子适用于clang trunk(w / libc ++)和GCC 4.7.2(w / libstdc ++),FWIW

2 个答案:

答案 0 :(得分:5)

  

有可能std::default_delete<MyUserDefinedType>实际上是std内部命名空间中定义的类的别名,在这种情况下,friend声明是非法的吗?

即可。根据C ++ 11标准的第20.7.1.1.2段:

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;
    };
}

明确指定必须是类模板的事实。这意味着它不能是别名模板。如果是这种情况,也不可能专门化它。

  

std::default_delete<MyUserDefinedType>是否可能调用其他实际执行删除的类,在这种情况下,朋友声明不会产生预期效果?

是。标准中没有任何内容指定某些内部帮助程序无法完成调用。根据第20.1.1.2段:

void operator()(T *ptr) const;
  

3效果:在delete上调用ptr

     

4备注:如果T是不完整的类型,则程序格式不正确。

这仅指定调用default_delete<>仿函数上的调用操作符的效果应该是什么,而不是如何具体实现(无论是直接在内部)调用操作符的主体,或者通过将任务委托给某个其他类的某个成员函数来实现。)

答案 1 :(得分:1)

  

通常,标准库中描述的类是直接实现其功能所必需的,还是可以使用任意级别的间接?

general 中,实现可以根据需要进行间接实现。有例如查看标准容器及其迭代器的实现 - 或者只是错误地使用它们,并查看错误消息中涉及的模板。但是,由于default_delete不是什么神奇的东西,它应该是一个单行,你可以期待它自己完成工作,但不能保证。

  

我的猜测是这是UB,但我很好奇,如果这是由标准专门解决的。

这不是UB,它只是未指明。

你可以确定你是否只是专门的default_delete<MyUserDefinedType> 允许专门化标准的图书馆模板),但我不会这样做。

我根本不会使用友谊,特别是如果涉及到没有专门化的模板的话。考虑一下:

//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!
}

朋友可以为你做任何事情。请谨慎选择。在这种情况下,我真的推荐一个自定义删除器 - 假设它有意义使析构函数私有,但通过删除器提供对它的访问。