设为首页 加入收藏

TOP

关于接口的设计与声明--对封装性的理解[C++](四)
2016-04-29 12:55:03 】 浏览:1102
Tags:关于 接口 设计 声明 封装 理解
ator() const; private: ... };
class Rational {
public:
    ...
    const Rational operator*(const Rational& rhs) const;
};

// 于是乎可以轻松实现乘法
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * oneEighth;  //  没问题
result = result * oneEighth;  //  没问题

到目前为止还没有实现致命问题,然而:

result = oneHalf * 2;  // ok!
result = 2 * oneHalf;  // error!
// result = 2.operator*(oneHalf);  of course wrong!

第一个式子能够成立,是因为实现了隐式类型转换。编译器知道你在传递一个int,而函数需要的是rational,但它也知道只要调用Rational构造函数并赋予你所提供的int,就可以变出一个适当的rational出来,于是就这么做了。相当于:

const Rational temp(2);
result = oneHalf * temp;

当然这只涉及non-explicit构造函数,才能这么做。如果是explicit构造函数,这个语句无法通过编译。

问题解决

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

只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者。地位相当于“被调用之成员函数所隶属的那个对象”-即this对象-那个隐喻参数,绝不是隐式转换的合格参与者。这就是为什么语句1能够通过编译而语句2不可以。

于是,方法就是,让operator*称为一个non-member函数,允许编译器在每一实参身上执行隐式类型转换。

const Rational operator*(const Rational& lhs, const Rational& rhs) {
    ...
}
Rational oneFourth(1, 4);
Rational result = oneFourth * 2; // right!
result = 2 * oneFourth; // right!

补充思考:
是否应该把该operator*声明为friend?

答案是否定的!请注意,member的反面不是friend,而是non-member!在此代码中,operator*完全可以借由rational的public接口完成任务,于是便不必把他声明为friend。无论何时,如果可以避免friend函数就应该避免。

总结:
如果你需要为某个函数的所有参数(包括this)进行类型转换,那么这个函数必须是个non-member。

8. 考虑如何写出特化的swap函数

swap作为STL的一部分,而后成为异常安全性编程的脊柱,以及用来处理自我赋值可能性的一个常见机制。由于此函数如此有用,也意味着他具有非凡哥的复杂度。本节谈论这些复杂度以及相应处理。

问题产生1

namespace std {
    template
   
     void swap(T &a, T &b) { T temp(a); a = b; b = temp; } }
   

这是标准程序库提供的swap算法。非常地简单,只要T有copying相关操作即可。然而这个算法对于有些情况却显得不那么高效。例如,在处理“以指针指向一个对象,内含真正数据”的那种类型。(这种设计的常见形式是所谓“pimpl手法:pointer to implemention)

class WidgetImpl {  //  实现细节不重要。
public:             //  针对Widget设计的class
    ...
private:
    int a, b, c;
    std::vector
   
     v; ... }; class Widget { public: Widget(const Widget& rhs); Widget& operator=(const Widget& rhs) { ... *pImpl = *(rhs.pImpl); ... } private: WidgetImpl* pImpl; };
   

对此类调用算法库的swap就会非常低效。因为他总共要复制三个Widget和三个WidgetImpl对象!而事实上,只需要改变指针的指向就可以了。

问题解决1

我们可能尝试用以下方法解决,让swap针对Widget特化。

尝试一:

namespace std {
    template<> // 表示他是std::swap的一个全特化
    void swap
   
    (Widget &a, Widget &b) { swap(a.pImpl, b.pImpl); } }
   

通常来说,我们是不能够改变std命名空间内的任何东西,但可以(被允许)为标准template制造特化版本的。

但实际上这个是无法通过编译的。因为他企图调用class的私有成员。
所以更合理的做法,是令他调用成员函数。

解法:

class Widget {
public:
    ...
    void swap(Widget& other) {
    using std::swap;
    swap(pImpl, other.pInmpl);
    }
    ...
};
private:
    WidgetImpl* pImpl;
};

namespace std {
    template<>
    void swap
   
    (Widget &a, Widget &b) { a.swap(b); } }
   

这个做法不仅能够通过编译,而且与STL容器有一致性。

问题产生2

假设Widget和WidgetImpl都是class template而非class,也许我们可以试试把WidgetImpl内的数据类型加以参数化:

template
   
     class WidgetImpl {...}; template
    
      class Widget {...}; // 在Widget里面放入swap成员函数就像以往一样简单 // 但在写特化std::swap时出现了问题 namespace std { template
     
       void swap< Widget
      
        > (Widget
       
        & a, Widget
        
         & b) { a.swap(b); } }
        
       
      
     
    
   

以上特化swap其实有问题的。我们企图偏特化这个function template,但C++只允许对class template偏特化。(随后会介绍全特化和偏特化)。当你尝试偏特化一个function template时,更常见的做法是添加重载函数:

namespace std {
    template
   
     void swap(Widget
    
     & a, Widget
     
      & b) { a.swap(b); } }
     
    
   

但实际上,这也是不行的!因为std是个特殊的命名空间,其管理规则比较特殊,客户可以全特化std内的template,但不可以添加新的template到std里面。

问题解决2

解决这个问题的方法就是,声明一个non-member swap让它调用member swap,但不在将那个non-member swap声明为std::swap特化版或重载版本。

namespace WidgetStuff {
    template
   
首页 上一页 1 2 3 4 5 下一页 尾页 4/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇nyoj 712 探 寻 宝 藏(双线dp 第.. 下一篇nyoj 711最舒适的路线(第六届河南..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目