在std命名空间中交友类,有什么保证吗?

6
这个问题是我回答这个问题时提出来的:标准是否允许并保证了使用friend关键字去友元化标准库类和/或函数?
在这个特定情况下,问题是:
class MyUserDefinedType
{
    friend struct std::default_delete<MyUserDefinedType>;

private:
    ~MyUserDefinedType() { }
}

保证将MyUserDefinedType存储在默认删除器的std::unique_ptr<MyUserDefinedType>std::shared_ptr<MyUserDefinedType>对象中。

一般来说,标准库中描述的类是否需要直接实现其功能,还是可以使用任意级别的间接方式?例如,是否可能:

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

或者

  • std::default_delete<MyUserDefinedType> 调用另一个实际执行删除操作的类,这种情况下 friend 声明将不会产生预期的效果?

或者其他类似情况?

我的猜测是,这并不能保证工作,但我想知道标准是否具体规定了这一点。

对于所给出的上述特定示例,在 clang trunk (w/libc++) 和 GCC 4.7.2 (w/libstdc++) 上都能工作。


我不知道答案,但是“与std::default_delete<>交朋友”可以让几乎任何人调用类的析构函数,如果它起作用的话。这让我想知道为什么首先将析构函数设为私有,然后使用友元声明使其(几乎)再次变为公共。 (不过,必须承认,这仍然意味着自动存储的对象无法被释放。) - jogojapan
我并不认为这是真的(虽然我十年前就不再是C++专家了);根据实验,最新的g++和vc++似乎并不是这样。 - Ron Burk
我认为这个解决方案不够好,建议实现一个新的删除器。 - Stephen Lin
这在使用PIMPL时可能是有道理的,@jogojapan,因为该类只对期望访问其成员的代码可见。例如,如果他将MyUserDefinedTypeImpl作为MyUserDefinedType的实现,则在理论上允许MyUserDefinedTypeImpl同时与MyUserDefinedTypestd::default_delete<MyUserDefinedTypeImpl>建立友好关系可能是有意义的,以提供私有继承的保护性和公共析构函数的清晰性。当然,这仍然不是理想的解决方案,但至少存在一个理论上的用例。 - Justin Time - Reinstate Monica
2个回答

5

在std的内部命名空间中定义了一个类,std::default_delete<MyUserDefinedType>可能是该类的使用别名,这种情况下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个作用:调用ptr上的delete

4个备注:如果T是不完整的类型,则程序是非法的。

这只是指定在调用default_delete<>函数对象的调用运算符时应该产生的效果,而不是具体实现如何完成此操作(无论是直接在调用运算符的主体中还是通过将任务委托给其他类的某个成员函数)。


1
@StephenLin:好的,那么我认为“未指明的行为”可能是一个更好的表达。 - Andy Prowl
1
@StephenLin:如果你的C++标准库实现可以编译,而且你不关心编写可移植代码,那么你是可以这样做的。你的程序没有UB(未定义行为)。但我认为你不应该期望它在所有实现中都能正常工作。 - Andy Prowl
1
@AndyProwl 好的,我想这有道理...我显然不认为这是一个好主意,如果明确禁止会更好,但我意识到他们无法堵住所有漏洞。 - Stephen Lin
2
@AndyProwl:你的第二点完美地指出了“friend”的问题:缺乏传递性。在某种程度上,它有助于缩小类私有接口更改的后果范围,但无法委派部分工作非常令人恼火,真正阻碍了组合和模块化 :( - Matthieu M.
1
@StephenLin:那是我的误解。正如@jogojapan所正确观察到的那样,它并不适用于这里。我仍然相信“Effects”中的语句意味着“最终在ptr上调用delete”,但并未说明从何处调用。 - Andy Prowl
显示剩余16条评论

1
一般来说,标准库中描述的类是否需要直接实现其功能,还是可以使用任意级别的间接性取决于具体实现。例如,查看标准容器及其迭代器的实现,或者错误消息中涉及的模板,就可以了解到这一点。但是,由于 default_delete 并不是什么神奇的东西,你可以期望它自己完成工作,但不能保证。
我猜这可能是未定义行为,但我想知道标准是否有明确规定。
这不是未定义行为,只是未指定行为。
如果你专门化了 default_delete,就可以确定了(允许专门化标准库模板),但我不会这样做。
我根本不会使用友元,特别是对于未经专门化的模板。请考虑以下情况:
//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!
}

朋友对你来说可以做任何事情。选择他们时要谨慎。在这种情况下,我真的建议使用自定义删除器 - 假设将析构函数设置为私有,但通过删除器提供访问权限是有意义的。


是的,我建议使用自定义删除器,但这不是我的推荐解决方案。 - Stephen Lin
+1 谢谢你的回复,虽然如此,我还是希望有人能找到一些关于friend与标准类/函数的非常权威的引用。 - Stephen Lin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接