第四章 设计与声明

条款18 让接口容易被正确使用,不易被误用


欲开发一个“容易被正确使用,不易被误用”的接口,首先必须考虑客户可能做出什么样的错误。我们设计如下代码:
 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Date
{
public:
    Date( int month,  int day,  int year);
    ...
};
//因为month和day以及year,容易输入错误,我们会限定客户的输入
class Month
{
public:
     static Month Jan()
    {
         return Month( 1);    //函数,返回有效月份
    }
     static Month Feb()
    {
         return Month( 2);    //这些是函数而非对象
    }
    ...
     static Month Dec()
    {
         return Month( 12);
    }
private:
     explicit Month( int m);         //阻止生成新的鱼粉,这是月份专属数据。
};
//我们可以这样调用
Date d(Month::Mar(), Day( 30), Year( 1995));

预防客户错误的另一个方法是,限制类型内什么事可做,什么事不能做。常见的限制是加上const。
例如,以const修饰operator*的返回类型可阻止客户因'用户自定义类型'而犯错:if(a*b=c)的错误。

例如动态分配对象时,客户也有可能出现错误。
 C++ Code 
1
Investment* createInvestment();  
    为了避免资源泄漏,createInvestment返回的指针最终必须被删除, 但那至少开启了两个客户端错误的机会:没有删除指针,或删除同一个指针超过一次。
     采用智能指针解决上面的方法。如auto_ptr,tr1::shared_ptr;
    tr1::shared_ptr 有一个特别好的性质是,它自动使用它的“每个指针专属的删除器”,因而消除另一个潜在的客户错误,所谓的“cross-DLL problem”。这个问题发生于“对象在动态链接程序库(DLL)中被new创建,却在另一个DLL内被delete销毁”。在许多平台上,这样的 cross-DLL new/delete 对会引起运行时错误。tr1::shared_ptr 可以避免这个问题,因为它的缺省的 deleter 只将 delete 用于这个 tr1::shared_ptr被创建的 DLL 中。这就意味着,例如,如果 Stock 是一个继承自 Investment 的类,而且 createInvestment被实现如下,
 C++ Code 
1
2
3
4
std::tr1::shared_ptr<Investment> createInvestment()
{
   return std::tr1::shared_ptr<Investment>( new Stock);
}

返回的 tr1::shared_ptr 能在 DLL 之间进行传递,而不必关心 cross-DLL 问题。指向这个 Stock 的tr1::shared_ptr 将保持对“当这个 Stock 的引用计数变为零的时候,哪一个 DLL 的 delete 应该被使用”的跟踪。

这个 Item 不是关于 tr1::shared_ptr 的——而是关于使接口易于正确使用,而难以错误使用的——但tr1::shared_ptr 正是这样一个消除某些客户错误的简单方法,值得用一个概述来看看使用它的代价。最通用的tr1::shared_ptr 实现来自于 Boost(参见 Item 55)。Boost 的 shared_ptr 的大小是裸指针的两倍,将动态分配内存用于簿记和 deleter 专用(deleter-specific)数据,当调用它的 deleter 时使用一个虚函数来调用,在一个它认为是多线程的应用程序中,当引用计数被改变,会导致线程同步开销。(你可以通过定义一个预处理符号来使多线程支持失效。)在缺点方面,它比一个裸指针大,比一个裸指针慢,而且要使用辅助的动态内存。在许多应用程序中,这些附加的运行时开销并不显著,而对客户错误的减少却是每一个人都看得见的。

Things to Remember

  • 好的接口易于正确使用,而难以错误使用。你应该在你的所有接口中为这个特性努力。
  • 使易于正确使用的方法包括在接口和行为兼容性上与内建类型保持一致。
  • 预防错误的方法包括创建新的类型,限定类型的操作,约束对象的值,以及消除客户的资源管理职责。
  • tr1::shared_ptr 支持自定义 deleter。这可以防止 cross-DLL 问题,能用于自动解锁互斥体(参见 Item 14)等。

条款19 设计class犹如设计type


设计高效的class必须考虑一下几个点:
新type的对象应该如何创建和销毁
对象的初始化和对象的赋值应有什么区别
新的type独享如果被passed by value以值传递对象意味什么,
什么是新的type的值
你的新type需要配合某个继承图系
你的type需要什么样的转换
什么样的操作符和函数对此新type而言是合理的
什么样的标准函数应该驳回
该取用type的心成员

条款20 宁可pass-by-reference-to-const 替换pass-by-value

      这个问题在在C++是非常常见的。传值和传引用巨大的差别在于你所使用的参数是其本身还是其副本。缺省情况下C++是以by value 传递的。其副本是对象本身的copy 构造函数自动调用的。有关对象自动调用的五个函数请看条款12.下面看一下例子吧 :
 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// refer_value.cpp : 定义控制台应用程序的入口点。

#include <iostream>
using  namespace std;
class Base
{
public:
    Base()
    {
        cout <<  "Base()" << endl;
    }
    Base( const Base &rhs)
    {
        cout <<  "Base copy" << endl;
    }
    ~Base()
    {
        cout <<  "~Base()" << endl;
    }
private:
    string str1;
    string str2;
};
class Derived:  public Base
{
public:
    Derived()
    {
        cout <<  "Derived()" << endl;
    }
    Derived( const Derived &rhs): Base(rhs)
    {
        cout <<  "Derived copy" << endl;
    }
    ~Derived()
    {
        cout <<  "~Derived()" << endl;
    }
     bool retTrue()  const
    {
         return  true;
    }
private:
    string str1;
    string str2;
};

bool validateDerived( const Derived d)   //传值
{
     return d.retTrue();
}
void test()
{

    Derived d;    //派生类
     bool bb = validateDerived(d);   //pass by value
}

int main()
{
    test();
    system( "pause");
     return  0;
}

运行结构如下:

从结果我们可以看到 构造函数,析构函数都调用了,其实远不止这些,
我们还看到基类的copy构造函数,派生类的copy构造函数,四次string copy构造函数以及每个对应的析构函数,总共多出测成本是六次构造函数和六次析构函数。12次的成本。也就是我们简单的一个调用,动用了这么多函数。很浪费资源。

下面我们用 pass-by-reference-to-const来。
修改代码为
 C++ Code 
1
2
3
4
5
6
7
8
9
10

bool validateDerived( const Derived &d)   //传引值
{
     return d.retTrue();
}
运行结果如下:


我们在来看看高效的传引用的方式,12次的成本就消失掉了。
by-reference避免了对象切割什么是对象切割,请看下边的例子。
 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
using  namespace std;
class Window   //基类
{
public :
    Window() {};
     void name() const;

     virtual  void display() const;
};
void Window::name() const
{
    cout <<  "window name" << endl;
}
void Window::display() const
{
    cout <<  " window display()" << endl;
}
class WindowWithScrollBars:  public Window   //派生类
{

public:
    WindowWithScrollBars() {};
     virtual  void display()  const;
};
void WindowWithScrollBars::display()  const
{
    cout <<  "WindowWithScrollBars display()" << endl;
}
void pintNaandWind(Window w)
{
    w.name();
    w.display();
}
int main()
{
    WindowWithScrollBars wwsb;
    pintNaandWind(wwsb); //打印结果变成基类的display()
    system( "pause");
     return  0;
}
打印结果,上面题目原意是要打印派生类的display的,结果变成了打印基类的display
可以看出,我们想得到WindowWithScrollBars display() ,但是我们得到的是上述的。它调用 是window对象,而不是子类的。因为他是by value,WindowWithScrollBars的所有特化信息都会被切除,这就是对象切割。那么解决办法就是 pass-by-reference-to-const,


更正之后结果就如期所愿


 C++ Code 
1
2
3
4
5
6
7
8
void pintNaandWind( const Window &w)

{

    w.name();

    w.display();
}

解释一下:
(1)所谓的slicing问题,也就是如果你把子类的对象赋值给父类的对象,如果用reference和指针,当然是可以的,而且这也是virtual的实现必需品。但是,如果用pass-by-value,就会出现,传进去的总是父类的对象,在传递对象的时候出现了切割问题。


(2)如果窥视c++编译器的底层,你会发现,references往往以指针实现出来,因此pass-by-reference通常意味着这真正传递的是指针。因此如果你有个对象属于内置类型(如int),pass by reference或pass by reference to const时,选择pass by value并非没有道理。这个忠告也适用于STL的迭代器和函数对象,因为习惯上它们都被设计为pass by value。迭代器和函数对象的实践者有责任看看他们是否高效且不受切割问题的影响。


(3)内置类型都相当的小。因此有人认为,所有小类型的types都是pass by value的合格候选人,甚至他们是用户自定义的class亦然。这是个不可靠的推论。对象小并不就意味其copy构造函数不昂贵。许多对象,包括STL容器,内含的东西只比一个指针多一些,但复制这种东西对象却需要承担“复制那些指针的每一样东西”。那将非常昂贵。

即使小型对象拥有并不昂贵的copy构造函数,还是可能有效率上的争议。某些编译器对待“内置类型”和“用户自定义类型”的态度截然不同,总是两者有相同的底层表述。举个例子,某些编译器拒绝把只由一个double组成的对象放进缓存器内,却很乐意在一个正规基础上对光秃秃的double那么做。当这种事发生,你更应该以by reference方式传递此等对象,因为编译器当然会将指针(references的实现体)放进缓存器内,绝无问题。


小型的用户自定义类型并不必然成为pass by value优良候选人的另一个理由是,作为一个用户自定义类型,其大小容易有所改变。甚至当你改用另一个c++编译器都有可能改变type的大小。举个例子,某些标准程序库实现版本中的string类型比其他版本大七倍!

请记住:
▲ 尽量以pass-by-reference-to-const替换pass-by-value.前者通常比较高效,并可避免切割问题(slicing
problem).
▲ 以上规则并不适合与内置类型,以及STL的迭代器和函数对象.对它们而言,pass-by-value往往比较适合.


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


当你理解条款21后,很可能出现一种过度使用的情况:只要看到是一个非内置类型,就去使用引用传值。举个书上的例子
 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
class Rational
{
private:
     int numerator;
     int denominator;
public:
    Rational(): numerator( 0), denominator( 1) {}
     friend  const Rational & operator* ( const Rational &r1,  const Rational &r2)
    {
        …
    }
};

numerator表示分子,denominator表示分母,这里我们需要把注意力放在operator*上,不是在它的形参上(用的const引用是完全正确的),而是在它的返回值上,这个带引用的返回值是一个巨大的bug。
我们可以设想一下实现:
 C++ Code 
1
2
3
4
5
6
7
friend  const Rational & operator* ( const Rational &r1,  const Rational &r2)
{
    Rational temp;
    temp.numerator = r1.numerator * r2.numerator;
    temp.denominator = r1.denominator * r2.denominator;
     return temp;
}

这是一种最容易想到的实现方式,在栈上创建对象temp,分子、分母分别相乘后,将之返回。

但仔细再想想,调用函数真的能收到这个temp吗?它是在operator*函数调用时的栈上产生的,当这个函数调用结束后,栈上的temp也会被pop掉,换言之,temp的生命周期仅存在于operator*函数内,离开这个函数,返回的引用将指向一个已经不在的对象!

对此,VS编译器已经给出了warning,如下:

“warning C4172: returning address of local variable or temporary”

千万不能忽略它。

那既然栈上创建对象不行,还可以在堆上创建嘛(new出来的都是在堆上创建的),于是我们有:
 C++ Code 
1
2
3
4
5
6
7
friend  const Rational & operator* ( const Rational &r1,  const Rational &r2)
{
    Rational *temp =  new Rational();
    temp->numerator = r1.numerator * r2.numerator;
    temp->denominator = r1.denominator * r2.denominator;
     return *temp;
}

这下VS编译器没有warning了,之前在资源管理那部分说过,new和delete要配对,这里只有new,那delete了?delete肯定不能在这个函数里面做,只能放在外面,这样new和delete实际位于两个不同的模块中了,程序员很容易忘记回收,而且给维护也带来困难,所以这绝对不是一种好的解决方案。书上举了一个例子,比如:

1 Rational w, x, y, z;
2 w = x * y * z;

连乘操作会有两次new的过程,我们很难取得operator*返回的reference背后隐藏的那个指针。

当然,如果把new换成auto_ptr或者是shared_ptr,这种资源泄露的问题就可以避免。

 

栈上创建的临时对象不能传入主调模块,堆上创建的对象就要考虑资源管理的难题,还有其他解决方法吗?

我们还有static对象可以用,static对象位于全局静态区,它的生命周期与这个程序的生命周期是相同的,所以不用担心它会像栈对象那样很快消失掉,也不用担心它会像堆对象那样有资源泄露的危险。可以像这样写:

 C++ Code 
1
2
3
4
5
6
7
friend  const Rational & operator* ( const Rational &r1,  const Rational &r2)
{
     static Rational temp;
    temp.numerator = r1.numerator * r2.numerator;
    temp.denominator = r1.denominator * r2.denominator;
     return temp;
}

这样写编译器同样不会报错,但考虑一下这样的式子:

1 Rational r1, r2, r3;
2 if(r1 * r2 == r1 * r3){…}

if条件恒为真,这就是静态对象做的!因为所有对象共享这个静态对象,在执行r1*r2时,temp的值为t1,但执行r1*r3之后,temp的值统一都变成t2了。它在类中只有一份,明白这个原因后就不难理解了。

 

既然一个static对象不行,那弄一个static数组?把r1*r2的值放在static数组的一个元素里,而把r1*r3放在static数组的另一个元素里?仔细想想就知道这个想法是多么的天马行空。

一个必须返回新对象的正确写法是去掉引用,就这么简单!
 C++ Code 
1
2
3
4
5
6
7
inline const Rational  operator* ( const Rational &r1,  const Rational &r2)
{
    Rational temp;
    temp.numerator = r1.numerator * r2.numerator;
    temp.denominator = r1.denominator * r2.denominator;
     return temp;
}

该让编译器复制的时候就要放手去复制,就像花钱买东西一样,必须花的终究是要花的。

 

最后总结一下:

绝对不要返回pointer或reference指向一个local stack对象,指向一个heap-allocated对象也不是好方法,更不能指向一个local static对象(数组),该让编译器复制对象的时候,就让它去复制!



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


主要考虑到封装性、统一性的问题。需要访问成员变量,则采用setter和getter函数来访问。
请记住:
    切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允许约束条件获得保证,并提供class作者以充分的实现弹性。
   protected 并不比public更具封装性。

条款23 宁以non-member、non-friend 替换member函数

思考下面的问题:

一个网页浏览器类

 C++ Code 
1
2
3
4
5
6
7
8
9
class WebBrowser
{
    public:
    ...
     void clearCache(); //清除Cache
     void clearHistory(); //清除History
     void removeCookies(); //清除Cookies
    ...
};
现在用户可能还希望清除所有(以上三种),因此WebBrowser提供了这样一个函数:
 C++ Code 
1
2
3
4
5
6
class WebBrowser
{
    ...
     void clearEverything();
    ...
};
当然我们也可以这么做:
 C++ Code 
1
2
3
4
5
6
void clearBrowser(WebBrowser &wb)
{
    wb.clearCache();
    wb.clearHistory();
    wb.clearCoockies();
}

现在的问题是哪个好?为什么?

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

   现在考虑对象里的数据。愈少的代码可以看到数据,愈多的数据被封装,而我们也就愈能自由的改变对象数据。数据被愈多的函数访问,数据的封装性就越低。

   As we know,成员变量应该为private,因为如果它不是,就有无限的函数去访问它,那么它就毫无封装性。能够访问private成员只有class的member函数和friend函数。那么现在我们已经知道了,clearBrowser函数比clearEverything函数更受到欢迎,因为它具有更大的封装性。

   这里有值得注意的地方,只因在意封装性而让函数“成为class的non-member”,并不意味着“不可以是另外一个class的member”。for example,我们可以让clearBrowser函数成为某个工具类的static member函数。只要它不是WebBrowser的一部分(或成为其friend),就不会影响WebBrowser的private成员的封装性。

C++的实际做法是:
 C++ Code 
1
2
3
4
5
6
7
8
9
namespace WebBrowserStuff
{
     class WebBrowser
    {
        ...
    };
     void clearBrowser(WebBrowser &wb);
    ...
}

将所有便利函数放在多个头文件中但隶属同一个命名空间,意味着客户可以轻松扩展这一组便利函数。他们所要做的就是添加更多non-member non-friend函数到此命名空间内。例如:如果客户想写些与影像下载相关的便利函数,只要在WebBrowserStuff命名空间内建立一个头文件,内含那些函数的声明即可。新函数就像其他旧函数一样可用且整合为一体。这是class无法提供的另一个性质,因为class定义对客户是不能扩展的。

请记住:

      拿non-member non-friend函数替换member函数。可以增加封装性包裹弹性(packaging flexibility)、和机能扩充性


条款24 若所有参数皆须类型转换,请为此采用non-member函数。


      令class支持类型隐式转换通常是个糟糕的主意。当然这条规定在建立数值类型时,有例外。假设一个class用来表现有理数,允许整数隐式转换为”有理数似乎很合理。
 C++ Code 
1
2
3
4
5
6
7
class Rational
{
public:
    Rational( int numerator =  0int denominator =  1);  //刻意不为explicit;允许int-to-Rational隐式转换
     int numerator() const;
     int denominator() const;
};

在支持算术运算符时考虑该由member函数、还是non-member函数来实现:

先看成员函数的写法:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Rational
{
public:

     const Rational  operator*( const Rational &rhs) const;
};

Rational oneEight( 18);

Rational onehalf( 12);

Rational result = oneHalf * oneEight; //nice

result = result * oneEight; //ok

//但是你希望支持混合运算:

result = oneHalf *  2; //ok 2发生了隐式类型转换。

result =  2 * oneHalf; //wrong !

//这样便一目了然:

result = oneHalf. operator * ( 2);  //ok

result =  2. operator * (oneHalf);  //wrong!
oneHalf是一个含operator*函数的class的一个对象。2没有相应的class,编译器会尝试寻找可被以下这般调用的non-member operator*(也就是在命名空间内 或 global作用域内):
 C++ Code 
1
result =  operator*( 2, oneHalf); //wrong!

本例不存在这样一个接受int和Rational作为参数的non-member operator* 因此查找失败。

只有当参数被列于参数列(parameter list)内,这个参数才是隐式类型转换的合格参与者地位相当于“被调用之成员函数所隶属的那个对象”——即this对象——的那个隐喻参数,绝不是隐式转换的合格参与者


解法:
为了支持混合运算。让operator* 成为一个non-member函数,便允许编译器在每一个实参身上执行隐式类型转换:
 C++ Code 
1
2
3
4
5
6
const Rational  operator*( const Rational &lhs,  const Rational &rhs)
{
     return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}

result =  2 * oneHalf; //ok!终于编译通过了!

operator*是否要成为Rational的friend函数呢?答案是否定的,因为operator*完全籍由Rational的public接口完成任务;无论何时如果你可以避免friend函数就该避免。

当让Rational成为一个class template, 又有一些新争议、解法、牵连形成了 条款46


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


条款25 考虑写出一个不抛出异常的swap函数。

我们可以调用std下的swap函数,这是一个模板函数:既可以:
 C++ Code 
1
2
3
4
int a =  1;
int b =  2;
std::swap(a, b);
cout <<  "a = " << a <<  " b = " << b << endl;
也可以(前提这个类型支持复制构造函数和赋值构造函数):
 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Test
{
public:
    Test( int i): val(i) {}
     int getVal()
    {
         return val;
    }
private:
     int val;
};

Test a( 1);
Test b( 2);
std::swap(a, b);
cout <<  "a = " << a.getVal() <<  " b = " << b.getVal() << endl;
return  0;
这个函数是是同通过类似int tmp = a; a = b; b = tmp的方法实现的,所以,如果类中的数据成员较多,这样的交换缺乏效率。
相比之下,“以指针指向一个对象,内含真正的数据”的方法更受欢迎。比如pimpl(pointer to implementation)。然后交换它们的指针。按照这种方法,我们应该这样设计我们的类:
 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//类的具体实现
class TestImpl
{
public:
    TestImpl( int i): ival(i) {}
     int getVal()
    {
         return ival;
    }

private:
     int ival;
};

//指针
class Test
{
public:
    Test( int i): p( new TestImpl(i)) {}
    ~Test()
    {
         delete p;
    }
    Test  operator=( const Test rhs)
    {
        *p = *(rhs.p);
    }
     int getVal()
    {
         return  this->p->getVal();
    }
     void swap(Test &other)
    {
         using std::swap;
        swap(p, other.p);
    }
private:
    TestImpl *p;
};
我们在Test类中,同过调用std::swap完成了指针的交换。为了是得我们的swap更像是std中的swap函数,我们将std中的swap特化:
 C++ Code 
1
2
3
4
5
6
7
8
namespace std
{
     template<>
     void swap<Test>(Test &a, Test &b)
    {
        a.swap(b);
    }
}
特化版本调用的就是类成员函数中的swap。


但是,如果Test和TestImpl都是类模板:
 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//类的具体实现
template < typename T1>
class TestImpl
{
public:
    TestImpl(T1 i): ival(i) {}
     virtual T1 getVal()
    {
         return ival;
    }

private:
    T1 ival;
};

//指针
template < typename T1>
class Test
{
public:
    Test(T1 i): p( new TestImpl(i)) {}
    ~Test()
    {
         delete p;
    }
    Test  operator=( const Test rhs)
    {
        *p = *(rhs.p);
    }
    T1 getVal()
    {
         return  this->p->getVal();
    }
     void swap(Test &other)
    {
         using std::swap;
        swap(p, other.p);
    }
private:
    TestImpl *p;
};

那么我们似乎需要这么改写原先的交换函数:

 C++ Code 
1
2
3
4
5
6
7
8
namespace std
{
     template< typename T1>
     void swap<Test<T1>>(Test<T1> &a, Test<T1> &b)
    {
        a.swap(b);
    }
}
但这并不合法,因为C++规定,不能偏特化一个函数模版。而这里却将swap的类型特化为了Test<T1> &。

有没有好的办法呢?其实很简单,只要把原来的函数重载就行了。

 C++ Code 
1
2
3
4
5
template< typename T>
void swap(Test<T> &a, Test<T> &b)
{
    a.swap(b);
}
剩下的问题就是吧这个函数放在哪里了?首先,设为全局函数是非常不好的,因为我们很有可能会经常调用swap函数的“平凡”版本。所以放在命名空间中是一个不错的选择。但是有两点需要注意:1.这个命名空间中也必须包括我们定义的模板类。2.不要放在std空间内。虽然放进去也能使用,但是std是C++标准委员会定义的,为了方便别人的使用,咱们还是放在咱们自己定义的空间中吧。


现在的考虑另一种情况:假如你在一个函数模板中需要调用swap函数,该怎么做呢?首先,你希望的情况是:最好调用专属的swap,如果不存在,那么调用std下的swap:
 C++ Code 
1
2
3
4
5
6
7
template < typename T>
void doSomething(T &obj1, T &obj2)
{
     //其他操作省略
     using std::swap;
    swap(obj1, obj2);
}
那么,根据名字查找规则,则会通过argument-dependent look先找出命名空间内的重载的swap,如果找不到,则再调用std内的。这里的using声明的作用只是让这个函数“曝光”,至于用不用,则是另一码事。但是如果你写成了using std::swap(obj1,obj2);就表明你肯定是要用std下的swap了。


总而言之:
1.虽然std下提供了一个swap函数,但是由于这个函数效率不高,所以我们倾向于在交换类时,通过pimpl技术交换指针。
2.首先,我们需要定义一个public swap函数,在这个函数中调用std下的swap函数完成指针的交换。
3.然后定义一个非成员swap函数,这个函数调用public swap。
4.假如的是类模板,那么,需要重载一个swap函数,然后将这个函数与模板类一起放在一个命名空间中。
5.调用swap时应使用using std::swap;声明。这样对于与重载的swap函数类型不符的函数,会调用std下的swap完成。














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值