条款20-25

条款20:宁以pass_by_reference替换pass_by_value

        缺省情况下C++以by value方式传递对象至函数,这可能使得pass_by_value成为费时的操作。而pass_by_reference可以避免构造和析构操作,并且by reference方式传递参数可以避免slicing(对象切割)问题。当一个derived class对象以by value方式传递并被视为一个base class对象,base class的copy构造函数会被调用,而造成此对象的行为像个derived class对象的那些特化性质全被切割,仅仅留下一个base class对象

class window{
public:
std::string name() const;
virtual void display() const;
};
class windowWithScrollBars:public window{
public:
virtual void display() const;
};
void printNameAndDisplay(window w)
{
std::cout<<w.name;
w.display();
}

        当调用上述函数,并且传递一个windowWithScrollBars对象时:

WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);

        参数w会被构造成一个window对象,它是pass by value,wwsb的所有特化信息都会被切除。在printNameAndDisplay函数内,不论传递过来的对象原本是什么类型,参数w就像一个window对象。因此printNameAndDisplay内调用dispaly调用的总是window::display,绝不会是windowWithScrollBars::display。解决办法就是by reference。

        对于内置类型,pass by value往往比pass by reference的效率高一些,这个忠告也适用于STL的迭代器和函数对象,因为习惯上它们都被设计为passed by value。但并不是所有小型的type都是pass_by_value的合格候选者,用户自定义的class也是如此。

请记住:

        尽量以pass_by_reference替换pass_by_value。前者通常比较高效,并可以避免切割问题

        以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言pass_by_value往往比较适当


条款21:必须返回对象时,别妄想返回其reference

        一旦领悟了pass_by_reference传递的效率,就想一味地使用pass_by_reference,但这往往会犯下致命错误:开始传递一些reference指向其实并不存在的对象。

class rational{
public:
rational(int numerator=0,int denominator=1);
private:
int n,d;
friend const rational operator*(const rational &lhs,const rational &rhs);
};

        该函数以pass_by_value返回计算结果,若非必要,没人想要为这样的对象付出太多代价,如果可以改用pass_by_reference就不需要付出代价,所谓reference只是一个名称,代表一个既有对象。任何时候看到一个reference声明式,你都应该立刻问自己,它的另一个名称是什么。

        我们当然不能期望这样一个rational对象在调用operator*之前就存在。如果operator*要返回一个reference指向如此数值,它必须自己创建那个rational对象。

        函数创建新对象的途径有二:在stack空间或者在heap空间。如果定义一个local变量,就是在stack空间创立:

const rational& operator*(const rational &lhs,const rational &rhs)
{
rational result(lhs.n*rhs.n);
return result;
}
        你的目标是要避免调用构造函数,但result却必须像任何对象一样由构造函数构造起来。更严重的是:这个函数返回一个reference指向result,但result是一个local对象,而local对象在函数退出之前被销毁了。 任何函数如果返回一个reference指向一个local对象,都将一败涂地。

        于是我们考虑在heap内构造一个对象,并返回reference指向它,heap_based对象由new创建,所以你得写一个heap-based operator*如下:

const rational& operator*(const rational &lhs,const rational &rhs)
{
rational *result=new rational(lhs.n*rhs.n);
return *result;
}

        你还是必须付出一个“构造函数调用”代价,并且,谁应该对这被你new出来的对象实施delete?这就会导致内存泄漏。

        上述不论on-the-stack或on-the-heap做法,都对operator*返回的结果调用结构函数而受到惩罚。

请记住:

        绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象。


条款22:将成员变量声明为private

        某些东西的封装性与当其内容改变时可能造成的代码破坏量成反比。

        假设我们有一个public成员变量,而我们最终取消了它,所有使用它的客户都会被破坏,因此public成员完全没有封装性。如果我们有一个protect成员变量,而我们最终取消了它,所有使用它的derived class都会被破坏,因此在这两种情况下,如果成员变量被改变,都会有不可预知的大量代码受到破坏。从封装的角度来看,只有两种访问权限:private(提供封装)和其他(不提供封装)。

请记住:

        切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允许约束条件或的保证,并提供class作者以充分的实现弹性

        protect并不比public更具封装性


条款23:宁以non_member、non_friend替换member函数

        如果某些东西被封装,它就不再可见。越多东西被封装,越少人可以看见它。而越少人可以看见它,我们就有愈大的弹性可以去改变它,因为我们的改变仅仅直接影响看到改变的那些人事物。因此,越多东西被封装,我们改变那些东西的能力也就越大。这就是我们首先推崇封装的原因:它使我们能够改变事物而只影响有限客户。

        越多函数可以访问它,数据封装性就越低。成员变量应该是private,因为如果它们不是,就是无限量的函数可以访问它们,它们就毫无封装性。因此,如果要在一个member函数和一个non_member、non_friend之间作抉择,而且两者提供相同的机能,那么导致较大封装性的时non_friend、non_member函数,因为它并不增加能够访问的class之内的private成分的函数数量。

        在这一点上有两件事值得注意。第一,这个论述只适用于non_friend、non_member函数。friend函数对class private成员的冲击力道相同,因此,这里选择的关键不在member和non_member函数之间,而是在member和non_member、non_friend函数之间。

        第二,只因在意封装性而让函数成为class的non_member并不意味着它不可以是另一个class的member。

请记住:

        宁以non_member、non_friend函数替换member函数。这样做可以增加封装性、包裹弹性、和机能扩充性


条款24:若所有参数皆需类型转换,请为此采用non_member函数

        再次参考rational class,将operator*函数编写为member函数:

class rational{
public:
const rational operator*(const rational &rhs) const;
};

        当我们尝试混合式运算时,会发现只有一半行得通:

rational result;
result=oneHalf*2;      //ok
result=2*oneHalf;      //error

        整数2并没有相应的class,也就没有operator*成员函数。编译器会尝试寻找可以被一下这般调用的non_member operator*(也就是在命名空间内或者global作用域内),但本例中并不存在一个接受int和rational作为参数的non_member operator*,因此查找失败。

        第一次调用成功是由于进行了隐式转换,而第二次并没能够进行隐式转化。只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者,而被调用的成员函数所属的那个对象——this对象——的那个隐喻参数绝不是隐式转换的合格参与者。

        因此,我们需要将member函数改写为non_member函数,至于他应不应该成为friend函数,视情况而定,上例中完全可由public接口完成操作,就不需要指定为friend函数。

        大多数C++程序员假定,如果一个与某class相关的函数不能成为member,就应该是一个 friend。这是错误的,无论如何能避免friend就避免,不能够只因函数不该成为member就自动让它成为friend

请记住:

        如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non_member



猜你喜欢

转载自blog.csdn.net/peakyblinder/article/details/79989480