尽管发布的两个答案都是正确的(并且我都赞成),但我想我会更深入地介绍这个问题,以供将来发现此问题的任何人使用。
“朋友”的含义
对于初学者来说,“朋友”对类中的函数有不同的含义。如果它只是一个函数声明,那么它将给定函数声明为类的朋友,并允许访问它的私有/受保护成员。然而,如果它是一个函数实现,则意味着该函数是 (a) 类的朋友,(b) 不是类的成员,并且 (c) 不能从任何封闭的命名空间中访问。IE。它成为一个全局函数,只能通过参数相关查找(ADL) 访问。
以下面的测试代码为例:
#include <iostream>
#include <iterator>
namespace nsp
{
template <class element_type, class element_allocator_type = std::allocator<element_type> >
class test_container
{
private:
element_type numbers[50];
friend class iterator;
public:
class iterator : public std::iterator<std::bidirectional_iterator_tag, element_type>
{
private:
element_type *i;
template <class distance_type>
friend void advance(iterator &it, distance_type n);
friend typename std::iterator_traits<iterator>::difference_type distance(const iterator &first, const iterator &last)
{
return last.i - first.i;
}
public:
iterator(element_type &_i)
{
i = &(_i);
}
element_type & operator *()
{
return *i;
}
element_type & operator = (const iterator source)
{
i = source.i;
return *this;
}
bool operator != (const iterator rh)
{
return i != rh.i;
}
iterator & operator ++()
{
++i;
return *this;
}
iterator & operator --()
{
--i;
return *this;
}
};
iterator begin()
{
return iterator(numbers[0]);
}
iterator end()
{
return iterator(numbers[50]);
}
template <class distance_type>
friend void advance(iterator &it, distance_type n)
{
it.i += 2 * n;
}
};
}
int main(int argc, char **argv)
{
nsp::test_container<int> stuff;
int counter = 0;
for (nsp::test_container<int>::iterator it = stuff.begin(); it != stuff.end(); ++it)
{
*it = counter++;
}
nsp::test_container<int>::iterator it = stuff.begin(), it2 = stuff.begin();
using namespace std;
cout << *it << endl;
++it;
cout << *it << endl;
advance(it, 2);
cout << *it << endl;
std::advance(it, 2);
cout << *it << endl;
int distance_between = distance(it2, it);
cout << distance_between << endl;
cin.get();
return 0;
}
如果从inside 调用main()
,advance()
ADL 将起作用,并且将调用类迭代器的自定义 Advance。但是,如果nsp::advance()
尝试nsp::test_container<int>::advance()
或stuff.advance()
,这些将导致编译错误(“没有匹配的函数调用”)。
模板问题
虽然确实会优先调用非模板函数重载而不是模板函数重载,但这与 ADL 的使用无关。无论函数是模板还是非模板,都会调用特定类型的正确重载。另外,advance()
特别需要一个距离类型的模板参数(int、long int、long long int 等),这是不可能跳过的,因为我们不知道编译器会从什么类型推断,比如“1000000 ",而且我们不知道程序员可能会抛出什么样的类型advance()
。幸运的是,我们不需要担心部分特化,因为std::advance()
它与我们的自定义Advance 在不同的命名空间中,并且可以简单地advance()
使用我们的硬编码迭代器类型实现我们自己的,如上面的示例所示。
如果我们的迭代器本身是一个模板并接受参数,这仍然有效 - 我们只需在高级模板中包含参数并以这种方式对模板的迭代器类型进行硬编码。例如。:
template <class element_type, class distance_type>
friend void advance(iterator<element_type>, distance_type distance);
更多模板问题(附注)
虽然这并不具体advance()
涉及到它的实现,但它通常与类友元函数的实现有关。distance()
您会注意到在上面的示例中,我直接在迭代器类中实现了非模板函数,而advance()
模板函数在迭代器类之外但在 test_container 类中被声明为友元。这是为了说明一点。
如果该类是模板(或模板的一部分),则您不能在与它成为朋友的类之外实现非模板友元函数,因为您的编译器会抛出错误。然而,模板函数advance()
可以在类外部声明,仅包含在朋友类中的定义。该advance()
功能也可以直接在朋友类中实现,为了说明这一点,我只是选择不这样做。
模板友元函数参数阴影
这与上面的示例无关,但可能是程序员进入模板友元函数的陷阱。如果您有一个模板类和一个对该类进行操作的友元函数,显然您将需要在函数定义和类中指定模板参数。例如:
template <class element_type, class element_allocator_type = std::allocator<element_type> >
class test_container
{
private:
element_type numbers[50];
public:
template <class element_type, class element_allocator_type>
friend void do_stuff(test_container<element_type, element_allocator_type> &container)
{
numbers[1] = 5; // or whatever
}
};
但是,上面的方法不起作用,因为编译器认为您对 'element_type' 和 'element_allocator_type' 使用相同的名称是对 test_container 定义中首先使用的模板参数的重新定义,并且会引发错误。因此,您必须为它们使用不同的名称。IE。这有效:
template <class element_type, class element_allocator_type = std::allocator<element_type> >
class test_container
{
private:
element_type numbers[50];
public:
template <class c_element_type, class c_element_allocator_type>
friend void do_stuff(test_container<c_element_type, c_element_allocator_type> &container)
{
numbers[1] = 5; // or whatever
}
};
仅此而已-我希望任何偶然发现此问题的人都能从中受益-大多数信息以某种方式,形状或形式散布在stackoverflow中,但是将它们组合在一起对新手很重要。
[更新:]即使上述所有内容,尽管 ADL 是“正确的”,但仍不足以将 ADL 正确解析为正确的函数。这是因为 clang、microsoft visual studio 2010-2013,可能还有其他人,在复杂模板中解析 ADL 时遇到困难,并且无论如何都可能崩溃或抛出错误。在这种情况下,明智的做法是简单地求助于迭代器类友好的标准容器函数。