C++朋友函数被类函数隐藏

C++ friend function hidden by class function?

本文关键字:类函数 隐藏 函数 朋友 C++      更新时间:2023-10-16

最小示例:

class A
{
    friend void swap(A& first, A& second) {}
    void swap(A& other) {}
    void call_swap(A& other)
    {
        swap(*this, other);
    }
};
int main() { return 0; }

g++4.7表示:

friend.cpp: In member function ‘void A::call_swap(A&)’:
friend.cpp:7:20: error: no matching function for call to ‘A::swap(A&, A&)’
friend.cpp:7:20: note: candidate is:
friend.cpp:4:7: note: void A::swap(A&)
friend.cpp:4:7: note:   candidate expects 1 argument, 2 provided

评论第4行:

// void swap(A& other) {}

而且效果很好。如果我想保留交换函数的两个变体,为什么以及如何解决这个问题?

我相信这是因为编译器试图在类中找到函数。这应该是一个最小的改变,使其工作(它在Visual Studio 2012中工作):

class A; // this and the next line are not needed in VS2012, but
void swap(A& first, A& second); // will make the code compile in g++ and clang++
class A
{
    friend void swap(A& first, A& second) {}
    void swap(A& other) {}
    void call_swap(A& other)
    {
        ::swap(*this, other); // note the scope operator
    }
};
int main() { return 0; }

作为一种变通方法,您可以声明swapstatic版本。然后,您可以声明friend版本来调用static版本。

class A
{
public:
    friend void swap(A& first, A& second) { A::swap(first, second); }
private:
    static void swap(A& first, A& second) {}
    void swap(A& other) {}
    void call_swap(A& other)
    {
        swap(*this, other);
    }
};
int main () {
    A a, b;
    swap(a, b);
}

为什么选择

在类内部,类范围内的名称隐藏了周围命名空间中的名称;因此,friend(其名称在命名空间中起作用,但在命名空间中不能直接访问)被成员(在类中起作用)隐藏,在这里不能作为潜在的重载使用。(更新:或者可能比这更复杂,正如评论中提到的。范围和名称查找规则有点难以遵循,尤其是当涉及朋友时)。

如果我想保留交换函数的两个变体,如何解决这个问题?

没有完美的解决方案。如果两个函数都做相同的事情,那么只使用其他成员函数中的成员。如果您在类定义外部声明友元,那么它可以作为::swap访问;但是,如果将类放在不同的命名空间中,这会有点脆弱。(Update:或者按照这个答案的建议使用静态成员函数;我没有想到)。

坚持标准的交换习惯用法,就不会有问题:

void call_swap(A& other) {
  using std::swap;
  swap(*this, other);
}

或者使用Boost.Swap包装:

void call_swap(A& other) {
  boost::swap(*this, other);
}

这相当于@Juan的解决方案,只是你不是自己编写助手。

您还可以使用助手函数,如本例中的

template<class T>
void swap_elems(T& lhs, T& rhs)
{
    using namespace std;
    swap(lhs, rhs);
}
class A
{
friend void swap(A& first, A& second) { first.swap(second); }
  public:
    void swap(A& other) {}
    void call_swap(A& other)
    {
        swap_elems(*this, other);
    }
};

您在这里观察到的是,在没有friend函数的先前声明的情况下,类中的friend ship会将名称注入封闭命名空间,但不会注入类范围。在类范围内发生的唯一事情是,名为的函数被授予访问私有属性的权限。

这只在类作用域中留下一个交换函数(具有一个参数的成员),因此这是唯一的候选重载。一旦你找到了一个候选者,即使重载解析失败,你也永远不会尝试另一个封闭范围(阴影)。

如果你真的需要这两个版本(并且退一步确保你需要),那么把实现放到一个函数中,比如swap_impl,你可以从朋友和成员那里调用它。