Effective c++ 读书总结 21-25

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


1、为什么我们会选择返回引用类型

因为返回引用类型可以避免以值返回造成的对象构造和析构带来的效率问题。特别是当一个对象内部资源很大的时候,以值返回往往造成效率降低。

2、什么情况下可以使用引用返回

当一个函数操作是对对象内部资源生效时,且需要返回生效之后的结果的时候。

class test
{
private:
    string str;
public:
    const string& append(const Test& s)
    {
        str += s.str;
        return str;
    }
};

3、什么情况应该以值返回

当函数执行需要一个临时结果的时候以值传递,此时不要想着返回一个在函数作用域的一个栈区变量的引用,因为当函数结束后新对象就会被销毁,就会产生引用悬空的情况。也不要向开辟一块堆区空间t = new test;然后返回它的引用,因为这时你要承受的有new可能带来的异常,并且把释放空间的任务交给调用者,然而调用者可能并不知情。更不要异想天开在对象内部定义一个static对象或是在该函数对应static对象,因为一个类的多个对象只包含一份static对象,所以static对象是一个。在类的成员里定义一个该类的对象用于返回这种引用也是一种方案,但是c++不允许在类成员中定义该类的non-static对象。

综上所述,在需要一个临时对象来承载这个操作的结果的时候选择值传递就好了,虽然可能使效率降低,但是可避免很多错误。

class test
{
private:
    int num;
public:
    test(int n = 0):num(n){}
    const test operator*(const test& x)
    {
        test t(num * x.num);    //存储临时结果
        return t;
    }

    const test& operator*(const test& x)
    {
        static test t;    //内部使用一个静态对象来充当临时变量,但是那会导致通过'*'操作符返回的
                          //是同一个static对象所以,当if(a *b == c * d);结果永远为true
        t.setNum(num * x.num);
        return t;
    }

    const test& operator*(const test& x)
    {
        test* t = new test(num * x.num);    //可能发生异常,也可能空间不能得到释放,像
                                    //test x = a * b * c;那么将得不到a*b所创建的对象引用
        return *t;
    }
};

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

1、为什么

将成员变量声明为private就意味着,除了通过该类提供的成员函数,外部是不可能访问到这个变量的。也就是通过使用private可能限制用户对数据的操作,并且也就意味着用户不知道具体的内部实现,那么当内部的实现方式改变更新,用户也不会察觉,这样就不会因为实现方式的升级而改变外部的所以接口的调用,减少对用户代码的影响。

class test
{
private:
    int num;
public:
    const int     getNum()
    {
        return num;
    }
};
//外部只可能对num变量进行读操作,不能更改,能更改的只能是内部的member函数和friend函数。
//改变接口
const int getNum()
{
    return num % 10;
}
//如果内部的具体操作改变了,用户调用的依然只是getNum这个函数。如果是非private的话就要改成
int n = t.num;        //更改前
int n = t.num % 10;   //更改后,需要在用户的所有用到num处都进行修改。

2、protected和public一样不具有封装性

我们都知道public修饰的成员可以在任意位置使用对象调用,protected修饰的成员只能在子类中使用,那么同样会出现上面描述的问题。如果成员改变那么所有的子类代码都要改变。所以能提供封装性的只有private。

条款二十三:宁以non-member,non-friend替换member函数


1、什么意思

不是说将所有的member都由non-member,non-friend替换,因为这也是不可能的,因为能直接访问private成员变量的只有member和friend。其意思是说将一些对象的辅助操作的工具类函数声明为non-member,non-friend,这样可以更好的提供封装性。另外把各种不同类型的工具函数放在不同的文件内,但是同属于一个命名空间,这样在使用时,只需要包含所需要工具的头文件就可以了,降低了编译的相依性。c++标准库就是这样实现的。所有的函数包含在同一个命名空间中,但是却分为多个文件。

class test
{
public:
    void     clearBuffer();
    void     release();
    void     close();
};
void     destroyTest(test& t)    //提供这样的工具类函数
{
    t.clearBuffer();
    t.release();
    t.close();
}

条款二十四:若所有参数皆需类型转换,请为此采用non-member函数


1、发生的场景

发生的场景主要是在定义类运算操作符时

class test
{
private:
    int a;
public:
    test(int x = 0):a(x){}
    const test operator*(const test& t)
    {
        return test(a * t.a);
    }
    
    int     get()
    {
        return a;
    }
};

test a(3);
test t = a * 4;    //4隐式转换成一个test对象,拷贝构造t,a对象调用‘*’操作符
test t2 = 4 * a;     //整数4没有带有test参数的‘*’操作符,编译出错。

//改为类外,这样就可以4*a运算了,但是此函数需要被声明为friend,因为内部直接访问了类的private成员
const test operator*(const Test& x,const Test& y)
{
    return test(x.a * y.a);
} 

//使用类提供的成员函数访问成员变量,不需要被声明为friend函数
const test operator*(const Test& x,const Test& y)
{
    return test(x.get() * y.get());
}

2、其中的问题

上面的例子之所以能成功,是因为test类的构造允许隐式类型转换,所以test t = a * 4;实际上是将4转换为test tmp(4);

只有当参数在参数列上,它才成为因为转换的合格参与者。

条款二十五:考虑写出一个不抛异常的swap函数


1、为什么需要自己去写,不是有std::swap吗

重新写自己的特化版是为了提高swap的效率。

class Person
{
private:
    shared_ptr<PersonImpl> pImple;    //通常为了更好的封装性在接口类中存在一个指向实际工作的指针
};
//现在程序设计普遍使用这种方法pointer to implementation
//所以普通的swap
<typename T>
void     swap(T& a, T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}
//而实际上只需要交换两个指针就好了
void     swap(Person& a, Person& b)
{
    swap(a.pImple,b.pImple);
}
//通常情况下我们需要为类提供一个成员swap函数而用外部的特化版本调用它。

2、如果Person是模板类型

<typename T>
class Person
{};

<typename T>
void     swap(Person<T>& a,Person<T>& b)
{
    a.swap(b);
}

3、另外在我们调用swap函数时,通常希望如果有该类型的特化版本就调用特化版本,如果没有就调用普通版。

那么就不要直接使用std::swap();这便是强迫编译器只在std命名空间中查找swap函数,而通常我们自定义的特化版本的swap并不在std中,因为c++禁止在std命名空间中声明那些已经声明好的东西。

int main()
{
    using std::swap;
    swap(a,b);    //替换std::swap,这样就会优先调用特化版本,当没有特化版本时会使用std中的
    return 0;
}

4、swap函数通常是我们躲避异常的堡垒,所以考虑写一个不抛出异常的swap成员函数,其他版本调用成员函数。

 

 

展开阅读全文

没有更多推荐了,返回首页