正文

模板类的友元模板函数2008-11-04 20:46:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/bioexplore/39238.html

分享到:

摘自:http://www.informit.com/

一下内容说明了定义友元时需注意的规则及模板类中友元模板函数的特殊语法结构:

friend Declarations PartI

Last updated Aug 19, 2005.

friend declarations have a few unintuitive properties that might confuse both beginners and experienced programmers. Some of the declarations use special syntactic forms, while others have an unusual semantics. Read all about friend declarations here.

The Essence of Friendship

A class may declare a function or another class as its friend. A friend has access to all the class' members, including private and protected ones. The name of a friend is not in the scope of the class. Here are two examples of friend declarations:

bool operator < (const A&, const A&);

class A
{
private:
 int x;
public:
 A();
 friend bool operator < (const A&, const A&);//friend function
 friend class B;
};
class B
{
public:
void func(int);
};

A friend declaration of a function consists of the keyword friend followed by the function's declaration. This way, it's possible to specify a single friend function from a set of overloaded functions:

int func(); //1
int func(const char *); //2
struct A
{
 friend int func();// applies only to #1
};

Any member of class B has full access to A's members. Similarly, the global overloaded operator < has access to every member of A. Notice that the access specifiers private, protected and public have no effect on friend declarations; the meaning of the friend declaration is the same in all three cases. However, it's customary to declare friends as public.

Friendship is neither transitive nor inherited. Thus, if A is declared as friend in B, B has no special privileges when referring to A's members. Similarly, classes derived from A cannot access non-public members of B, unless these derived classes are also declared as friends in B:

class D: public A
{
};
struct S{};
class B
{
 friend class A; //base
 friend class D; //derived
 friend struct S;
}

A friend declaration of a class must contain an elaborated type specifier, i.e. one of the keywords struct, class, or union followed by the type's name, unless the befriended class has been declared previously:

struct G{};
class F
{
 friend struct B; //OK, using elaborated type specifier
 friend G; //OK, G has already been declared
};
struct B{};

Definitions in a friend Declaration

Oddly enough, C++ permits definitions of functions within a friend declaration. However, you cannot define types within a friend declaration:

struct A
{
 friend bool operator < (const A& a1, const A& a2) 
 { return a1.i < a2.i;} //OK, function defined within a friend declaration
 friend class X {}; //error, type definition isn't allowed here
private:
 int i;
};

Now there's an interesting question: What is the status of a function defined within a friend declaration? Outwardly, the operator < defined in class A looks like a member function of this class. However, this doesn't make sense at all, since every member function has unlimited access to members of its class anyway.

Another hint pertaining to the status of such a function is the parameter list. The overloaded operator < takes two parameters, as does the global version thereof which you saw earlier.

All right, I won't keep you in suspense any longer: the C++ standard says that every function defined within a friend declaration is never a member function of the class granting friendship. Thus, defining the overloaded operator< inside a friend declarations is the same as defining the overloaded operator< outside the class. C++ imposes the following restrictions on a function defined within a friend declaration:

  • The function shall have namespace scope
  • The function shall not have a qualified name:
    class X
    {
     friend int A::func() {return 0;} //error
    }
  • The class granting friendship shall not be a local class.

The common practice is to define such functions outside the class and use their prototype in a friend declaration. The Standard Library uses this style, as does every decent textbook.

Combining the function definition and the friend declaration is bad programming practice, for at least two reasons:

  • Readability. Most readers (and quite a few C++ compilers, it appears) might mistake it for a member function.
  • Decoupling. Since the function isn't an integral part of a class' interface, there's no reason to define it inside the class -- doing so would force clients to recompile the class whenever the function is modified.

Fine-tuned friendship

A class may grant friendship to a member function of another class. In this case, other member functions of the latter class have no special privileges:

struct A
{
 void f(struct B& );
 void g(struct B& );
};
struct B
{
 friend void A::f(struct B&);
};

A function first declared in a friend declaration has external linkage. Otherwise, the function retains its previous linkage:

static int func(); 
struct A
{
 friend int func(); //func retains its static linkage
 friend int func2(); //func2 assumed to be extern
};

In the second part of this article I will discuss template friend declarations, showing how to declare class templates, function templates, members of a class template and specializations of a class template as friends.

friend Part II: the Interaction of Friendship and Template Classes

Last updated Aug 26, 2005.

Continuing our discussion of friend, it's time to see how to befriend templates. C++ enables you to control precisely which specialization(s) of a given template are granted friendship. However, this fine-grained control sometimes necessitates intricate syntax.

Declaring non-template Functions and Classes as friends of a Class Template

Declaring an ordinary class or function as friends of a class template isn't different from the friend declarations I've shown before. Here is an example of a class template that declares class Thing and func() as its friends:

class Thing {};
void func (int);

template <class T> class X
{
public:
 //...
 friend class Thing;
 friend void func (int);
};
X <int> xi;
X<std::string> xs;

In each specialization of X, func(), and Thing are friends.

Class Templates

When you declare a class template X as friend of class Y, every specialization X is a friend of Y. If Y itself is a class template, every specialization of X is a friend of every specialization of Y:

template <class U> class D{/*...*/};
template <class T> class Vector
{
public:
 //...
 template <class U> friend class D;
};

Here, every specialization of D is a friend of every specialization of Vector.

Template Specializations

You may restrict friendship to a particular specialization of a class template. In the following example, the class template Vector declares the specialization C<void*> as its friend:

template <class T> class C{};
template <class T> class Vector
{
public:
 //...
 friend class C<void*>; 
};

In every specialization of Vector, e.g., Vector<int>, Vector<std::fstream> etc., C<void*> is friend. However, other specializations of C such as C<int>, C<std::string *> etc., aren't.

Function Templates

You may also declare a function template as friend. Suppose you want the overloaded operator== to be a function template used by Vector. This ensures that for every Vector<T>, the compiler will generate a matching operator==<T>.

Declaring a function template as friend consists of three steps. First, forward declare both the class template granting friendship and the function template:

template <class T> class Vector; //forward declaration of class template 
// forward declaration of friend function template
template <class T> bool operator == (const Vector<T>& v1, 
                   const Vector<T>& v2);

Next, declare the function template as friend inside the class template:

template <class T> class Vector
{
public:
 friend bool operator==<T> (const Vector<T>& v1, const Vector<T>& v2); 
};

Finally, define the function template:

template <class T> bool operator== (const Vector<T>& v1, 
                  const Vector<T>& v2)
{ 
//..
}

You can avoid these three steps by defining the function template within the friend declaration. In this case, the forward declarations aren't necessary.

template <class T> class Vector
{
public:
  //defining the friend function template inside the class
 friend bool operator== (const Vector<T>& v1, const Vector<T>& v2)
 {//..
 }
};

The template parameter T in operator==<T> and Vector<T> co-varies. That is, the compiler generates operator==<int> for the specialization Vector<int>, operator==<Date> for Vector<Date>, and so on. What if you want to declare only one specialization of a function template as friend? To do so, forward declare as before the class template and the function template. Then add a friend declaration to the class:

template <class T> class Vector; 
template <class T> bool operator == (const Vector<T>& v1, 
                   const Vector<T>& v2);
template <class T> class Vector
{
public:
friend bool operator==<Date> (const Vector<Date>& v1, //specialization
                     const Vector<Date>& v2); 
};

Notice that the template argument Date appears in angle brackets after the function's name. Date also replaces the template parameter T in the function's parameter list.

Finally, define the specialization somewhere in the program:

template <> bool operator==<Date> (const Vector<Date>& v1, 
                  const Vector<Date>& v2)
{
 //..
}

Remember that a definition of a specialization is preceded by the sequence 'template <>'.

Unlike a primary function template, a specialization of a function template cannot be defined within a friend declaration. As usual, you may declare multiple specializations of the same function template as friends:

template <class T> class Vector
{
public:
 friend bool operator==<int> (const Vector<int>& v1, 
                      const Vector<int>& v2); 
 friend bool operator==<Foo> (const Vector<Foo>& v1, 
                      const Vector<Foo>& v2); 
 friend bool operator==<char *> (const Vector<char *>& v1, 
                      const Vector<char *>& v2); 

};

Friendship and Design

I've focused exclusively on the syntactic properties of friendship, but not on the design issues. friend declarations are traditionally frowned upon in the literature since they allegedly violate encapsulation. This criticism isn't justified, though. Unquestionable, judicious usage of friendship is necessary for robust design.

However, in many cases, a friend declaration enables you to enhance encapsulation by restricting access to a class' implementations details. The alternative, i.e., defining a get() member function that every client can use indiscriminately can be much worse. I will discuss friendship and design in an upcoming article.

阅读(4104) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册