C++GOW系列之(4):类机制

原文请连接http://www.gotw.ca/gotw/004.htm

你对编写类的细节有多了解?本章GOW不仅关注公然的错误,更加关注专业风格。

问题

你正在做代码审查。一个程序员写下了如下的类,其中显示了代码的不良风格以及一些切实的错误。你能找到多少,你如何修正他们?

class Complex {
    public:
        Complex( double real, double imaginary = 0 )
          : _real(real), _imaginary(imaginary) {};

        void operator+ ( Complex other ) {
            _real = _real + other._real;
            _imaginary = _imaginary + other._imaginary;
        }

        void operator<<( ostream os ) {
            os << "(" << _real << "," << _imaginary << ")";
        }

        Complex operator++() {
            ++_real;
            return *this;
        }

        Complex operator++( int ) {
            Complex temp = *this;
            ++_real;
            return temp;
        }

    private:
        double _real, _imaginary;
    };
答案:

前言:

这个类包含的错误远超过表面显示出来的。这个谜题的重点首先是观察类的机制(比如说“<<操作符的规范格式是什么?” “+操作符应该成为一个成员函数么?”)而不是仅仅指出接口的哪些方面是不良的设计。然而,我将使用一个非常有用的评论作为开始,#0..

0. 既然标准库已经有了Complex,为什么还要重写一个?(标准库中的实现没有任何以下的问题并且被那些业界最优秀的人基于多年的实践而编写。 谦虚点,重用他们)

    【指南】重用标准库中的算法而不是手动重写他们,他们更快,更容易,并且更安全。

    【博主观点】GOW作者重复强调重用标准库,说明这已然是一个在C++领域广泛存在的问题。是广大的Coder不熟悉标准库呢,还是他们想锻炼或证明自己的实力而不去用呢?这引申出另外一个问题,在面对已有的,成熟的,被广泛使用的库时, 按部就班的使用接口会让自己觉得没有学到实实在在的东西,而去深入的探究库的内部实现时,又觉得山高水远,路途漫漫,于是很多人选择了实现一个符合自己口味的简化版,可能正是这样的原因,才导致了作者要重复强调标准库的重用。学习并有所收货的最佳途径一定要去实践,但是在亲自实践之前,花些时间去鉴赏大师们的经验之作,去探究大作底层的点点滴滴,虽然耗时颇多,但获益更多。不良风格的代码写多了,自然水平也难以长进。

 class Complex {
    public:
        Complex( double real, double imaginary = 0 )
          : _real(real), _imaginary(imaginary) {};
1. 风格:这可以被用作一个单参数的构造函数,产生一个隐式转型。这往往不是想要的结果。

    【指南】谨慎悄无声息的转型。一种好的避免隐式转型的方式就是当可能的时候使用显式转型。

    【博主观点】这是第二个GOW作者一再强调的指导原则,作者用watch out来强调其警告程度。小心注意隐式转型,如果他不是你故意为之,那么就可能产生你意料不到的结果

 void operator+ ( Complex other ) {
            _real = _real + other._real;
            _imaginary = _imaginary + other._imaginary;
        }
2. 风格:从效率上考虑,参数应该写成const&,而“a=a+b”应该写成“a+=b”

    【规则】尽量使用const&而不是值拷贝

    【博主观点】作者使用尽量而不是一定,我想作为读者的你一定了解吧,你应该不会写const int16_t&(在32位以上平台)这种东西吧

    【指南】在数学运算中尽量使用“a op= b” 而不是“a = a op b”(在适当的地方,一些你写的类,但不是全部,并没有遵从op 和 op=的自然关系,是吧?)

3. 风格:+操作符不应该是一个成员函数。如果它是成员函数,那么像这样,你只能写“a = b + 1”而不能用“a = 1 + b”。从效率上考虑,你应该提供operate+(Complex, int)以及operate+(int, Complex)

    【规则】使用这些指南来决定一个操作是成员函数还是非成员函数:

        -一元操作符是成员函数

        -= () []以及->必须是成员函数

        -+= -= *= /=是成员函数

        -其它的二元操作符都是非成员函数

4. 错误:+操作符不应该改变这个对象的值。它应该返回一个临时对象来包含和值。注意,它的返回值必须是const Complex 而不仅仅是Complex,来防止诸如“a+b=c”这种代码

(实际上,原始的代码语义更接近于+=操作符而不是+)

5. 风格:当你定义了op以后,你应该定义op=。在这里,由于你定义了+操作符,你应该定义+=操作符。在这个例子中,上面的函数无论如何都是+=操作符的语义。

 void operator<<( ostream os ) {
            os << "(" << _real << "," << _imaginary << ")";
        }
(注意:对于<<操作符而言,你应该做一些事情比如说检查stream流的当前格式化标志来确保正确的使用。查阅你最喜欢的书籍去看细节性描述,推荐的有 Steve Teale's "C++ IOStreams Handbook", Glass and Schuchert's "The STL <Primer>", and Plauger's "The (Draft) Standard C++ Library"

6. 错误: <<操作符不应该是成员函数,并且参数应该是 "(ostream&, const Complex&)"。注意,正如James Kanze指出,不应该把<<操作符作为友元,而是应该调用类似"print"的公开成员函数。

7. 错误:这个函数的返回值类型应该为"ostream&",所以最后一行应该是"return os“,来允许链式使用。(如”cout<<a<<b“)

    【规则】在<<和>>操作符中,应该返回流的引用。

 Complex operator++() {
            ++_real;
            return *this;
        }
8.风格:前置递增运算符应该返回Complex&来让客户可以直观的进行更多操作。

 Complex operator++( int ) {
            Complex temp = *this;
            ++_real;
            return temp;
        }
9.风格:后置递增运算符应该返回const Complex,以便阻止类似”a++++“这种使用。

10.风格:更好的方式是使用前置递增运算符来实现后置递增运算符。

    【指南】使用前置递增运算符来实现后置递增运算符

    【博主观点】尽量遵从DRY原则(don't repeat yourself)

 private:
        double _real, _imaginary;
    };
11.风格:避免变量名以下划线开头。是的,我曾经习惯这样使用,而且非常出名的”设计模式“一书也这样使用。但是C++标准保留了一些以下划线为开头的关键字。他们难于记忆,所以你应该在你的新代码中避免这样使用。(因此我再也不允许以下划线开头的名字出现在成员变量中,取而代之的是以下划线结尾的变量名)

这就是全部了。这里是一个修正版,忽略掉了上面没有提及的设计和风格上的错误。

class Complex {
    public:
        explicit Complex( double real, double imaginary = 0 )
          : real_(real), imaginary_(imaginary) {}

        Complex& operator+=( const Complex& other ) {
            real_ += other.real_;
            imaginary_ += other.imaginary_;
            return *this;
        }

        Complex& operator++() {
            ++real_;
            return *this;
        }

        const Complex operator++( int ) {
            Complex temp = *this;
            ++(*this);
            return temp;
        }

        ostream& print( ostream& os ) const {
            return os << "(" << real_
                      << "," << imaginary_ << ")";
        }

    private:
        double real_, imaginary_;
        friend ostream& 
        operator<<( ostream& os, const Complex& c );
    };

    const Complex operator+( const Complex& lhs,
                             const Complex& rhs ) {
        Complex ret( lhs );
        ret += rhs;
        return ret;
    }

    ostream& operator<<( ostream& os,
                         const Complex& c ) {
        return c.print(os);
    }

你可能感兴趣的:(C++,GOW,类的风格)