【Effective C++】设计与声明——成员变量和成员函数
将成员变量声明为private
为什么成员变量不该是public?
(1)从语法一致性来说,如果成员变量不是public,就需要通过成员函数访问成员变量。public接口内的每样东西都是函数的话,客户就不需要在访问class成员时考虑要不要加小括号。(2)如果成员变量是public,任何人都可以读写它,而设为private后,可以更加精确地控制它,可以实现“不准访问”、“只读访问”以及读写访问。(3)封装,比如class中有一个成员变量mean,可以用成员函数来访问它(也就是封装了它),这样你可以替换不同的实现方式,客户最多只需重新编译。封装非常重要,你对客户隐藏成员变量(也就是封装了它们),你可以确保class的约束条件总是会获得维护,因为只有成员函数可以影响到它们。如果不隐藏,即使有class的源码,改变public事物还是会受到束缚,因为会破坏很多客户代码。public意味着不封装,不封装就意味着不可改变。protected并不比public更具封装性。
宁以non-member、non-friend替换member函数
想象有一个class表示网页浏览器,其中有几个函数用来清除缓存、历史记录和cookies:
class WebBrowser { public: ... void clearCache();//清除缓存 void clearHistory();//清除历史记录 void removeCookies();//清除cookies ... };
许多用户想一键清除缓存、历史记录和cookies,因此再提供一个函数:
//方法1 增加一个成员函数 class WebBrowser { public: ... void clearEverything();//调用clearCache、clearHistory和removeCookies ... }; //方法2 增加一个非成员函数 void clearBrowser(WebBrowser& wb){ wb.clearCache(); wb.clearHistory(); wb.removeCookies(); }
那么哪种方法更好呢?方法2 非成员函数的方法更好。为什么呢?
(1)非成员函数的封装性更高。如果一些东西被封装,它就不再可见。越多东西被封装,越少人可以看见它;越少人看见它,就有越大的弹性改变它。对于数据来说,越少的代码可以访问它,封装性就越高。当成员变量为private时,只有成员函数和友元函数可以访问它。方法1增加的成员函数clearEverything()不止可以访问class内的private函数,还可以取用private函数、enums、typedefs等等;而方法2的非成员函数,无法访问上述的东西,两者机能相当,因此选择并不增加“能够访问class内之private成分”的函数数量。
(2)方便机能扩充。在c++中,比较自然的做法是作为非成员函数并位于一个namespace中:
//头文件“webbrowser.h” 针对class WebBrowser自身 namespace WebBrowserStuff{ class WebBrowser{...} ...//核心机能,所有客户都需要的,非成员函数 } // 头文件 “webbrowserbookmarks.h” namespace WebBrowserStuff{ ... //与书签相关的函数 } // 头文件 “webbrowsercookies.h” namespace WebBrowserStuff{ ... //与cookies相关的函数 }
比如客户要再加影音下载相关的函数,只要写个头文件内含其声明即可
值得注意的是,因为在意封装性我们选择了非成员函数,但这并不意味着它“不可以是别的class的成员函数”,可以写一个工具类,clearBrowser()作为工具类的static member函数。
若所有参数皆需类型转换,请为此采用非成员函数
假设有一个类表示有理数:
class Rational { public: Rational(int numerator = 0, int denominator = 1) :n(numerator), d(denominator) {}; int numerator() const { return n; }//分子的访问函数 int denominator() const { return d; }//分母的访问函数 const Rational operator*(const Rational& rhs) const; private: int n, d; }; const Rational Rational::operator*(const Rational& rhs) const { return Rational(this->n * rhs.n, this->d * rhs.d); }
这样的设计可以让有理数相乘:
Rational oneEight(1, 8); Rational oneHalf(1, 2); Rational result = oneEight * oneHalf;
但是当你进行混合算数时:
Rational result = oneEight * 2; //可以 Rational result = 2 * oneEight; //错误
由于整数2没有相应的operator*函数所以报错。而oneEight * 2发生了隐式类型转换,将2转变为Rational。这是因为构造函数为non-explicit的,编译器才会自动转换,如果构造函数是explicit的,上两句都报错。
如果你想让他支持混合式算术运算,就让operator*成为非成员函数:
const Rational operator*(const Rational& r1, const Rational& r2) { return Rational(r1.numerator() * r2.numerator(), r1.denominator() * r2.denominator()); }
无论何时如果可以避免friend函数就该避免,因为就像真实世界一样,朋友带来的麻烦旺旺多过价值。