10

我知道关键字在封装方面的一般用例,friend但有几次,我需要friend关键字只是为了“完成工作”。这些用例并不让我高兴,所以我想知道是否有其他选择。这是第一个最小的示例:

struct Foo{
  enum class Bar{
    a=1,b=2,c=4
  };

  // need to tell the compiler of operator| before it gets used
  // but it can't be a member function of Foo: so add friend keyword  
  friend Bar operator|(const Bar& b1, const Bar& b2);  

  // constructor needs a default value using 
  // operator| for Bars 
  Foo( Bar b = Bar::a | Bar::b );
};

// definition of operator|, etc.

在构造函数声明中给出默认值之前,编译器有什么方法可以在接口内部查看嵌套类声明operator|Foo

我有时也会发现自己friend在定义模板中嵌套类的对称操作时使用了关键字。例如:

template<typename T>
struct A{
  struct B{
    friend bool operator==(const B& x, const B& y) { return true; }  
  };  
};

从封装的角度来看,operator==它不需要友谊。但由于operator==实际上不是模板化函数,并且编译器无法推断模板中嵌套类的类型,这似乎是operator==作为自由函数保留的唯一合理“技巧”。

正如我所说,这些选择确实有效,但我想知道是否有更好的选择/实践。

4

1 回答 1

4

事实上,我会说这是完全传统的。正如 Evg 所说,隐藏的朋友有特殊的好处,可见的朋友也很棒!

为了使这更像是一个答案,请考虑例如 libstdc++ 的std::unreachable_sentinel_t. 这可以作为end()一个无界的“生成器范围”返回,例如 a std::ranges::iota_view{0}。它与您的第二个示例非常相似:

struct unreachable_sentinel_t
{
    template<weakly_incrementable _It>
    friend constexpr bool
    operator==(unreachable_sentinel_t, const _It&) noexcept
    { return false; }
};

inline constexpr unreachable_sentinel_t unreachable_sentinel{};

常规迭代器和哨兵被定义为嵌套类并且也依赖于友谊,尽管它们的比较运算符通常确实需要特权访问。但是,即使您可以提供外联定义,在模板中内联朋友的另一个好处是您不必重复确切的模板标题,包括潜在的复杂约束。

如果有帮助,在 C++ 中,您可以将这些自由函数(无论它们是否是朋友)视为其参数类的公共接口的一部分,这是由于参数相关的查找。因此,即使您在技术上不需要友谊的特权,授予它也不会破坏封装。

只要您意识到隐藏友谊的细微差别,只要friend完成工作就使用!

于 2021-12-21T00:14:29.237 回答