For much of this chapter, we’ve been preaching the virtues of keeping your data private. However, you may occasionally find situations where you will find you have classes and functions outside of those classes that need to work very closely together. For example, you might have a class that stores data, and a function (or another class) that displays the data on the screen. Although the storage class and display code have been separated for easier maintenance, the display code is really intimately tied to the details of the storage class. Consequently, there isn’t much to gain by hiding the storage classes details from the display code.
我们已经吹了很多牛逼关于让你的数据保持私有访问的好处。然而你有时候会遇到一些情形,某个类和另一个类工作联系很紧密。举个栗子,你可能有一个类要存储数据,还有一个函数(或者类)显示这些数据到屏幕上。尽管存储数据的类和显示数据的代码为了更好的维护而分开,但是显示代码仍然和存储类的细节有很强的联系。所以,把存储类的细节藏起来不给显示代码看,其实没有多少好处。
In situations like this, there are two options:
在这种情况下,你有两种选择
- Have the display code use the publicly exposed functions of the storage class. However, this has several potential downsides. First, these public member functions have to be defined, which takes time, and can clutter up the interface of the storage class. Second, the storage class may have to expose functions for the display code that it doesn’t really want accessible to anybody else. There is no way to say “this function is meant to be used by the display class only”.
第一种就是让显示数据的代码使用存储数据的类暴露出来的函数。然而这样做有一些不好的地方。首先是这些Public成员函数得去被实现,这需要花时间去做,而且会弄乱存储类的样子。然后就是存储类为显示代码暴露的Public函数也会暴露给别的类,别的一些你不想要让他们去摸我的数据的类。没有办法去限制别的类不要碰我的这个Public函数。
- Alternatively, using friend classes and friend functions, you can give your display code access to the private details of the storage class. This lets the display code directly access all the private members and functions of the storage class, while keeping everyone else out! In this lesson, we’ll take a closer look at how this is done.
另外你可以使用一个Friend类或者Friend函数,这样做的话你的显示代码就可以访问存储数据类的私有成员。显示代码可以直接访问所有的私有成员和私有函数,而别的类却不能这样做。
A friend function is a function that can access the private members of a class as though it were a member of that class. In all other regards, the friend function is just like a normal function. A friend function may be either a normal function, or a member function of another class. To declare a friend function, simply use the friend keyword in front of the prototype of the function you wish to be a friend of the class. It does not matter whether you declare the friend function in the private or public section of the class.
Friend 函数是一个可以访问某个类的私有成员的函数,这是通过将自身作为那个类的成员来实现的。在所有别的方面,Friend函数就跟普通函数一样。一个Friend函数可以是普通函数,也可以是某个类的成员函数。要声明一个Friend函数的话,只需要使friend关键字放在函数原型前面就可以了。一个Friend函数的访问控制并不重要, Public或者Private。
Here’s an example of using a friend function:
class Accumulator
{
private:
int m_value;
public:
Accumulator() { m_value = 0; }
void add(int value) { m_value += value; }
// Make the reset() function a friend of this class
friend void reset(Accumulator &accumulator);
};
// reset() is now a friend of the Accumulator class
void reset(Accumulator &accumulator)
{
// And can access the private data of Accumulator objects
accumulator.m_value = 0;
}
int main()
{
Accumulator acc;
acc.add(5); // add 5 to the accumulator
reset(acc); // reset the accumulator to 0
return 0;
}
In this example, we’ve declared a function named reset() that takes an object of class Accumulator, and sets the value of m_value to 0. Because reset() is not a member of the Accumulator class, normally reset() would not be able to access the private members of Accumulator. However, because Accumulator has specifically declared this reset() function to be a friend of the class, the reset() function is given access to the private members of Accumulator.
在这个例子中,我们声明了一个叫reset的Friend函数。这个函数接受一个Accumulator对象,设置传入参数的m_value值为0。因为reset不是Accumulator类的成员(从函数定义的地方没有类名限定可以看出),通常来说reset是不能访问Accumulator对象的私有成员的。但是因为这个函数被Accumulator声明为一个Friend函数,那么这个函数就可以访问这个类的私有成员。
Note that we have to pass an Accumulator object to reset(). This is because reset() is not a member function. It does not have a *this pointer, nor does it have an Accumulator object to work with, unless given one.
我们必须传递一个Accumulator对象进来,因为reset并不是成员函数,他没有this指针,没有可以使用的Accumulator对象,除非你给他一个。设想你已经有了一份分开的显示代码和存储类,你只需要在存储类里面添加显示代码的prototype并且前置一个friend关键字就可以了,很方便。
Here’s another example:
class Value
{
private:
int m_value;
public:
Value(int value) { m_value = value; }
friend bool isEqual(const Value &value1, const Value &value2);
};
bool isEqual(const Value &value1, const Value &value2)
{
return (value1.m_value == value2.m_value);
}
In this example, we declare the isEqual() function to be a friend of the Value class. isEqual() takes two Value objects as parameters. Because isEqual() is a friend of the Value class, it can access the private members of all Value objects. In this case, it uses that access to do a comparison on the two objects, and returns true if they are equal.
在这个例子中我们声明了isEqual作为Value类的朋友,isEqual比较两个传入的Value对象的私有成员m_value,如果相等返回true,否则false。
While both of the above examples are fairly contrived, the latter example is very similar to cases we’ll encounter in chapter 9 when we discuss operator overloading!
上面的两个例子都是人为设计的,后者和我们将要在第九章遇到的运算符重载非常相似。
A function can be a friend of more than one class at the same time. For example, consider the following example:
一个函数可以同时是多个类的朋友。
#include <iostream>
class Humidity;
class Temperature
{
private:
int m_temp;
public:
Temperature(int temp=0) { m_temp = temp; }
friend void printWeather(const Temperature &temperature, const Humidity &humidity);
};
class Humidity
{
private:
int m_humidity;
public:
Humidity(int humidity=0) { m_humidity = humidity; }
friend void printWeather(const Temperature &temperature, const Humidity &humidity);
};
void printWeather(const Temperature &temperature, const Humidity &humidity)
{
std::cout << "The temperature is " << temperature.m_temp <<
" and the humidity is " << humidity.m_humidity << '\n';
}
int main()
{
Humidity hum(10);
Temperature temp(12);
printWeather(temp, hum);
return 0;
}
There are two things worth noting about this example. First, because PrintWeather is a friend of both classes, it can access the private data from objects of both classes. Second, note the following line at the top of the example:
上面的例子有两件事值得注意。第一,因为PrintWeather 是两个类的朋友,他可以访问两个类的对象的私有成员。第二,注意上面例子中最上面的那个类声明,也就是下面这单行代码。
class Humidity;
This is a class prototype that tells the compiler that we are going to define a class called Humidity in the future. Without this line, the compiler would tell us it doesn’t know what a Humidity is when parsing the prototype for PrintWeather() inside the Temperature class. Class prototypes serve the same role as function prototypes -- they tell the compiler what something looks like so it can be used now and defined later. However, unlike functions, classes have no return types or parameters, so class prototypes are always simply class ClassName
, where ClassName is the name of the class.
这是一个类的原型声明,告诉编译器我们将会定义一个叫Humidity 的类。如果没有这一行的话,编译器就会告诉我们它不知道Humidity是什么东西,在它试图解析在Temperature里面的PrintWeather函数原型的时候。这行代码告诉编译器这个Humidity看起来像什么,所以可以现在先用,稍后再定义。然而,类不像函数有返回类型和参数,所以类的原型总是简简单单的那个样子。
It is also possible to make an entire class a friend of another class. This gives all of the members of the friend class access to the private members of the other class. Here is an example:
之前是函数成为类的朋友,让一个类成为另一个类的朋友也是可以的。这样做会让类A的所有成员能够访问把A当做是朋友的类B的所有私有成员。
#include <iostream>
class Storage
{
private:
int m_nValue;
double m_dValue;
public:
Storage(int nValue, double dValue)
{
m_nValue = nValue;
m_dValue = dValue;
}
// Make the Display class a friend of Storage
friend class Display;
};
class Display
{
private:
bool m_displayIntFirst;
public:
Display(bool displayIntFirst) { m_displayIntFirst = displayIntFirst; }
void displayItem(const Storage &storage)
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
};
int main()
{
Storage storage(5, 6.7);
Display display(false);
display.displayItem(storage);
return 0;
}
Because the Display class is a friend of Storage, any of Display’s members that use a Storage class object can access the private members of Storage directly. This program produces the following result:
就像Friend函数一样,在某个类里面写一行声明前置一个friend关键字。类也是一样在另一个类里面写一个原型前置一个关键字friend。因为Display是Storage的朋友,所以Display的成员如果接受一个Storage类对象作为参数的话,就可以直接访问传入对象的私有成员
6.7 5
A few additional notes on friend classes. First, even though Display is a friend of Storage, Display has no direct access to the *this pointer of Storage objects. Second, just because Display is a friend of Storage, that does not mean Storage is also a friend of Display. If you want two classes to be friends of each other, both must declare the other as a friend. Finally, if class A is a friend of B, and B is a friend of C, that does not mean A is a friend of C.
尽管Display是Storage的朋友,Display没有直接访问Storage对象this指针的许可。这是因为Display是Storage的朋友,但这不意味着Storage是Display的朋友(朋友关系不相互,太真实了)。如果你想让两个类互相成为朋友,那么你得在每个类里面去声明朋友关系。最后,朋友关系没办法传递,A是B的朋友(A在B那里注册了),B是C的朋友,但这并不意味着A是C的朋友(A不可以拿C家里的东西,但是可以拿B家里的)。
Be careful when using friend functions and classes, because it allows the friend function or class to violate encapsulation. If the details of the class change, the details of the friend will also be forced to change. Consequently, limit your use of friend functions and classes to a minimum.
在使用Friend函数和类的时候要小心一点,因为这个机制破坏了封装。如果类的细节改变,那么friend的细节也被强迫随之改变。因此,最大程度的限制你的friend用法,朋友少一点。
Instead of making an entire class a friend, you can make a single member function a friend. This is done similarly to making a normal function a friend, except using the name of the member function with the className:: prefix included (e.g. Display::displayItem).
不需要将整个类认作是朋友,你可以将某个类的某个成员函数认作朋友。想法上这个目的和简单的认普通函数做朋友很相似,除了需要多加一个类名限定。
However, in actuality, this can be a little trickier than expected. Let’s convert the previous example to make Display::displayItem a friend member function. You might try something like this:
然而事实上可能要比预期复杂一些。
class Display; // forward declaration for class Display
class Storage
{
private:
int m_nValue;
double m_dValue;
public:
Storage(int nValue, double dValue)
{
m_nValue = nValue;
m_dValue = dValue;
}
// Make the Display::displayItem member function a friend of the Storage class
friend void Display::displayItem(const Storage& storage); // error: Storage hasn't seen the full definition of class Display
};
class Display
{
private:
bool m_displayIntFirst;
public:
Display(bool displayIntFirst) { m_displayIntFirst = displayIntFirst; }
void displayItem(const Storage &storage)
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
};
However, it turns out this won’t work. In order to make a member function a friend, the compiler has to have seen the full definition for the class of the friend member function (not just a forward declaration). Since class Storage hasn’t seen the full definition for class Display yet, the compiler will error at the point where we try to make the member function a friend.
然而这样做不可行。为了让类A的一个成员函数成为类B的朋友,编译器必须看到类A的全部定义,不能仅仅是一个前向声明。因为Storage没有看到Display全部的定义,当我们尝试使Display成员函数成为Storage朋友时,编译器将报错。
Fortunately, this is easily resolved simply by moving the definition of class Display before the definition of class Storage.
幸运的是,只需将类Display的定义移到Storage类的定义之前,就可以轻松解决此问题。
class Display
{
private:
bool m_displayIntFirst;
public:
Display(bool displayIntFirst) { m_displayIntFirst = displayIntFirst; }
void displayItem(const Storage &storage) // error: compiler doesn't know what a Storage is
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
};
class Storage
{
private:
int m_nValue;
double m_dValue;
public:
Storage(int nValue, double dValue)
{
m_nValue = nValue;
m_dValue = dValue;
}
// Make the Display::displayItem member function a friend of the Storage class
friend void Display::displayItem(const Storage& storage); // okay now
};
However, we now have another problem. Because member function Display::displayItem() uses Storage as a reference parameter, and we just moved the definition of Storage below the definition of Display, the compiler will complain it doesn’t know what a Storage is. We can’t fix this one by rearranging the definition order, because then we’ll undo our previous fix.
但是我们现在有另一个问题,因为Display::displayItem()使用Storage 作为参数,我们刚刚把Storage 的定义放到了Display下面,编译器抱怨不知道Storage是什么。我们不能通过排序解决这个问题。
Fortunately, this is also fixable in a couple of simple steps. First, we can add class Storage as a forward declaration. Second, we can move the definition of Display::displayItem() out of the class, after the full definition of Storage class.
幸运的是,这也可以通过几个简单的步骤解决。首先,我们可以添加Storage类作为前向声明。然后,在Storage类定义结束之后,再实现Display::displayItem函数,不要在Storage类细节没有告诉编译器的情况下,在displayItem函数里面直接使用Storage的私有成员。
简单总结就是,将声明前置,类定义前置,类成员函数的实现后置,更具体的讲就是友元函数。因为友元函数使用Storage的成员,所以要在Storage类定义后面。
Here’s what this looks like:
#include <iostream>
class Storage; // forward declaration for class Storage
class Display
{
private:
bool m_displayIntFirst;
public:
Display(bool displayIntFirst) { m_displayIntFirst = displayIntFirst; }
void displayItem(const Storage &storage); // forward declaration above needed for this declaration line
};
class Storage // full definition of Storage class
{
private:
int m_nValue;
double m_dValue;
public:
Storage(int nValue, double dValue)
{
m_nValue = nValue;
m_dValue = dValue;
}
// Make the Display::displayItem member function a friend of the Storage class (requires seeing the full declaration of class Display, as above)
friend void Display::displayItem(const Storage& storage);
};
// Now we can define Display::displayItem, which needs to have seen the full definition of class Storage
void Display::displayItem(const Storage &storage)
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
int main()
{
Storage storage(5, 6.7);
Display display(false);
display.displayItem(storage);
return 0;
}
A friend function or class is a function or class that can access the private members of another class as though it were a member of that class. This allows the friend or class to work intimately with the other class, without making the other class expose its private members (e.g. via access functions).
一个友元函数可以访问把它当成朋友的类的私有成员,通过在类内添加前置了friend关键字的函数原型或者类原型的方法来把别人当做朋友。这允许一个类和它的朋友亲密的合作而不需要暴露它的私有成员出来给所有别的。
Friending is uncommonly used when two or more classes need to work together in an intimate way, or much more commonly, when defining overloading operators (which we’ll cover in chapter 9).
Note that making a specific member function a friend requires the full definition for the class of the member function to have been seen first.
需要注意的是将特定的成员函数认作朋友,需要先看到这个类的定义。如果只使用ClassName,那么有一个前向声明就可以了。如果需要使用ClassName::MemberFunc,就需要先看到类的完整定义,否则编译器没办法工作。