effective C++学习(Designs and Declarations)

Designs and Declarations

Item 18: Make interfaces easy to use correctly and hard touse incorrectly.

1.     Firstly, you must consider themistakes use may take and then to restrict the values users enter.

struct Day

{

   explicit Day(int day):val(day)

   {}

   int val;

};

struct Month

{

   explicitMonth(int month):val(month){}

   int val;

};

struct Year

{

   explicitYear(int year):val(year){}

   int val;

};

class Date

{

public:

   Date(constMonth& month, const Day& day, const Year& year)

   :m_month(month),m_day(day),m_year(year) {}

private:

   Month m_month;

   Day m_day;

   Year m_year;

};

int main()

{

   Date date(30,3,1995);//error!

   Datedate(Day(30),Month(3),Year(1995));//error!

   Date date(Month(3),Day(30),Year(1995));//OK!

}

Note: this way can avoid some errors suchas the red statements.

2.     Replacing objects with functions.

class Month

{

public:

   static MonthJan(){return Month(1);}

   static MonthFeb(){return Month(2);}

   static MonthMar(){return Month(3);}

private:

   explicitMonth(int month):m_month(month){}

   int m_month;

};

 

Datedate(Month::Mar(),...);

Note: Where the Month class can’t beconstructed by users due to the private constructor, but can be assigned andcopy own to the assignment operator and copy constructor compiler provide. Itcan be constructed in it. You can also know more about the point in the Item04.

3.     Try your best to make theoperators in your classes consistent with the built-in types such as ints.

4.     As to the smart pointertr1::shared_ptr:

1)     Let the factory functionsreturn std::tr1::shared_ptr, to avoid forgetting to assign the returned pointerto smart pointer.

std::tr1::shared_ptr<Investment>createInvestment();

2)  Ifthe objects require the specific function to be released, we can let thefactory functions return tr1::shared_ptr equipped with the user-defined deletefunctions. E.g.

std::tr1::shared_ptr<Investment>createInvestment()

{

  std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0)

     , getRidOfInvestment);

  retVal = ...;

  return retVal;

}

                     Note:     a. the 0 is int type, so we need totransform it into Investment*.

b. if we canconfirm the Investment* before the “retVal” is defined, it will be better thanthe “null”.

3)      The tr1::shared_ptr can solve the “cross-DLL” problem, because itsdelete function is appointed in the DLL where it is declared.

Item 19: Treat class design as type design.

1.     Designing a perfective class,you have to consider the following questions:

1)     Constructors and destructors,operator new, operator new[], operator delete, operator delete[]

2)     Consider the difference betweeninitialization and assignment.

3)     Consider the situation about “passedby value”, which is achieved by copy constructors.

4)     The legal values of new types.

5)     Inheritance graph(继承图系):e.g. virtual destructors

6)     Conversion between new type andother types. Implicit conversion and explicit conversion.

7)     The operators and memberfunctions

8)     Which member function should bedeclared private member.

9)     Public, protected, private,friends, static etc.

10) Undeclared interface.

11) Consider whether you need todefine a class template.

12) Consider whether you need a newtype.

Item 20: Prefer pass-by-reference-to-const topass-by-value.

1.     Pass-by-value will call copyconstructors many times.

class Person

{

public:

   Person(){}

   Person(constPerson&){}

   virtual~Person(){}

private:

   std::string name;

   std::string address;

};

class Student:public Person

{

public:

   Student(){}

   Student(constStudent&){}

   ~Student(){}

private:

   std::string schoolName;

   std::string schoolAddress;

};

bool validateStudent(Student s);

Studentplato;

bool platoIsOk = validateStudent(plato);

Note: the pass-by-value above will call Person copyconstructor one time, Student copy constructor one time and “string”constructor four times. Destructing the temporary object, their destructorswill be called total six times. This way waste much computational time.

2.     Pass-by-reference-to-constdoesn’t call copy destructors. But you should remember that const can’t beforgotten. Because pass-by-value does not modify the real parameters by thecopying the origin parameters, while the pass-by-reference-to-const ensure notto change the origin parameters by const.

3.     Pass-by-value may also bringthe slicing problem.

class Window

{

public:

std::string name()const;

virtual void display() const;

};

class WindowWithScrollBars: publicWindow

{

public:

virtual void display()const;

};

void printNameAndDisplay(Window w)

{

std::cout<<w.name();

w.display();

}

WindowWithScrollBarswwsb;

printnameAndDisplay(wwsb);

Note: pass-by-value only constructs thebase part and slice the derived part. However, if we use “const Window& w”,we can avoid the problem.

4.     As to built-in type, STLiterator and function object, pass-by-value is more effective thanpass-by-reference-to-const because they are placed in buffer when running.

5.     Reference is implemented bypointer, while the pointer is built-in type, so its effect is better.

Item 21: Don’t try to return a reference when you mustreturn an object.

1.     Never return pointer orreference pointing local stack-based objects, while you should also returnreference pointing heap-allocated objects, or pointer or reference pointinglocal static objects.

constRational& operator*(const Rational& lhs, constRational& rhs)

{

    Rationalresult(lhs.cdata*rhs.cdata, lhs.cdata*rhs.cdata);

    //error! the local object will be destructed before thereturning the object.

    return Rational;

}

constRational& operator* (const Rational& lhs, constRational& rhs)

{

    Rational*pResult = new Rational(lhs.cdata*rhs.cdata,lhs.cdata * rhs.cdata);

    //error! the delete operator is ensured to be executed.

    //in particular, for the multi * operators

    return Rational;

}

constRational& operator*(const Rational& lhs, constRational& rhs)

{

    static Rational resutl;

    result=...;

   //error! Make multi-threadingsafety problem. And make the operator //== return true all the way.

    return result;

}

Note: the three ways above is allincorrect, so we need to consider the question before to use the reference toreturn object. The following way is appropriate.

inline const Rational operator* (const Rational& lhs, const Rational& rhs)

{

    return Rational(lhs.cdata*rhs.cdata,lhs.cdata*rhs.cdata);

}

The fact we have to admit is that the waycalls the constructors but avoids making mistake. Of course, the returningreference is allowed in some case, and it can bring better effect if you canuse it correctly, such as overload = operator.

Item 22: Declare data members private.

1.     Private provides theencapsulation(封装性) and other access permissions donot have encapsulation. We will explain the conclusion by the following points.

1)     Writing consistency. Accessingmember by member functions does not require us to consider whether to add “()”.

2)     Improving the scalability andflexibility of system. In the functions we can modify the private variables,such as notifying other variables when the variables are accessed, verifyingconstraints(约束条件), the condition of function,before and after it be called and multithreaded synchronization.

3)     Protested don’t have betterencapsulation than public.

4)     The system with betterencapsulation will bring less code that needs to be modified.

Item 23: Prefer non-member, non-friend functions to memberfunctions.

1.     Preferring non-member、non-friend functions to member functions can bring betterencapsulation(封装性), packaging flexibility(包裹弹性) and scalability.

2.     The more functions that haveaccess to member functions and variables are, the worse its packagingflexibility and encapsulation are.

3.     Where the non-member andnon-friend functions are based on the classes in which functions may bevisited, but they can be “static member function” in other classes.

4.     The namespace is used topackage non-member and non-friend functions, which can cross files. Thespecific implement ways is to use header files.

Item 24: Declare non-member functions when typeconversions should apply to all parameters.

1.     The following code tell us thatwe should declare non-member functions when type conversions should apply toall parameters.

class Rational

{

public:

    explicit Rational(constint numerator, constint denominator){}

    Rational(const double data)

    {

       this->m_data = data;

    }

    Rational(){}

    Rational(const Rational& rational)

    {

       this->m_data = rational.m_data;

    }

public:

    const Rational operator*(const Rational& lhs)

    {

       this->m_data *= lhs.m_data;

       return *this;

    }

private:

    double m_data;

};

int main()

{

    Rationalone(1,2);

    Rationalresult;

    result= one*2;

    result = 2*one;

    return 0;

}

Note: when we declare * operator as amember function, the statement result = 2*one; doesn’t pass compiler, because the first parameter is “2” ratherthan Rational. If the first parameter is Rational, the compiler will find the *operator in class Rational and implicitly convert the “2” into Rational by the constructor.Of course, if there is no the corresponding constructor, the statement is notalso passed.

2.     We can solve the problemthrough declaring * operator as non-member function.

const Rational operator* (constRational& lhs, const Rational& rhs)

{

}

Note: The friend function can also solvethis problem but it breaks the encapsulation. Because the friend function canvisit the private members in “lhs” and “rhs”, so we should try not to usefriend function.

Item 25: Consider support for a non-throwing swap.

1.     Additional knowledge points:template specialization and partially specialize.

Template specialization is the operator that appointers outinstantiating type, which is both for class and function template.

Partially specialization is to specialize in the partialtype in class template or specialize in the member functions in class, which isonly effective for class template.

2.     In order to ensure the effectof swap, especially to the objects that contain much data, we use a new objectto save data and define its pointer in our class. Then when we should copyobject, the only thing we should do is to copy the pointer, which is defined as“pimpl” (pointer to implementation).

#include <iostream>

#include <vector>

template<typename T>

void swap(T&a, T&b)

{

        T temp(a);

        a = b;

        b = temp;

}

class WidgetImpl

{

public:

        WidgetImpl(constint& a, const int& b, const int& c )

       :m_a(a),m_b(b),m_c(c){}

        friendstd::ostream& operator<<(std::ostream& out, const WidgetImpl&widgetimpl)

        {

               out<<widgetimpl.m_a<<""<<widgetimpl.m_b<<" "<<widgetimpl.m_c;

                return out;

        }

private:

        int m_a, m_b, m_c;

       std::vector<double> v;

};

class Widget

{

public:

        Widget(WidgetImpl*const& pdata):m_data(pdata){}

        void swap(Widget&other)

        {

               std::swap(m_data, other.m_data);

        }

        friendstd::ostream& operator<<(std::ostream& out, const Widget&widget)

        {

                returnout<< *widget.m_data;

        }

private:

        WidgetImpl * m_data;

};

namespace std

{

template<>

void swap<Widget>(Widget& lhs, Widget& rhs)

{

        lhs.swap(rhs);

}

 

int main()

{

        Widgetwidget1(&WidgetImpl(1,2,3)),widget2(&WidgetImpl(4,5,6));

       std::cout<<widget1<<""<<widget2<<std::endl;

       std::swap(widget1,widget2);

       std::cout<<widget1<<""<<widget2<<std::endl;

}

3.     If the “Widget” and “WidgetImpl”is template classes rather than classes and you want to partially specializethe template function, as shown in the following, but partially specializing atemplate function is not allowed, which is only designed for template classes.

Namespace std

{

       template<typename T>

        void swap<Widget<T>> (Widget<T>&a, Widget<T>&b)

               {a.swap(b);}//error!

}

4.     Anything is not allowed to beadded to std, what we can only do is to specialize templates. As consequence,overloading the templates in std is not allowed. We can use a non-member swapfunction to call the member function in a user-defined namespace.

namespace WidgetStuff

{

template<typename T>

class WidgetImpl

{

public:

        WidgetImpl(constT& a, const T& b, const T& c )

        :m_a(a),m_b(b),m_c(c){}

        friendstd::ostream& operator<<(std::ostream&out, const WidgetImpl<T>& widgetimpl)

        {

               out<<widgetimpl.m_a<<""<<widgetimpl.m_b<<""<<widgetimpl.m_c;

                returnout;

        }

private:

        T m_a, m_b, m_c;

        std::vector<double>v;

};

template<typename T>

class Widget

{

public:

        Widget(WidgetImpl<T>* const& pdata):m_data(pdata){}

        voidswap(Widget& other)

        {

                std::swap(m_data,other.m_data);

        }

        friendstd::ostream& operator<<(std::ostream&out, const Widget<T>& widget)

        {

                returnout<< *widget.m_data;

        }

private:

        WidgetImpl<T> * m_data;

};

template <typename T>

void swap(Widget<T>& lhs, Widget<T>& rhs)

{

   lhs.swap(rhs);

}

}

int main()

{

   usingWidgetStuff::WidgetImpl;

        WidgetStuff::Widget<int> widget1(&WidgetStuff::WidgetImpl<int>(1,2,3)),

          widget2(&WidgetImpl<int>(4,5,6));

        std::cout<<widget1<<" "<<widget2<<std::endl;

        swap(widget1,widget2);

        std::cout<<widget1<<" "<<widget2<<std::endl;

}

Note: NO1: when the friend functions callprivate members in the template class, the compiler would show the errorsbecause the type “T” is not loaded. The “T” will attach to a special type whenprogram running. NO2: according to name lookup rules (argument-dependent lookupor Koenig lookup), compiler firstly seeks in the namespace where argument lies.

5.     Member swap must not throwexception, because the member swap is based on “pimpl”, which is an operator tobuilt-in objects.

6.     Defining the friend templatefunctions or friend template classes in template classes, we firstly need todeclare the friend template functions or classes.

template<typename> class WidgetImpl;//declare template class

template<typename T>std::ostream& operator<<(std::ostream& out, constWidgetImpl<T>& widgetimpl);//declare template functions.

template<typename T>

class WidgetImpl

{

public:

        WidgetImpl(constT& a, const T& b, const T& c )

        :m_a(a),m_b(b),m_c(c){}

        friendstd::ostream& operator<<<T>(std::ostream& out, constWidgetImpl<T>& widgetimpl);

private:

        T m_a, m_b, m_c;

        std::vector<double>v;

};

template <typename T>std::ostream& operator<<(std::ostream&out, const WidgetImpl<T>& widgetimpl)

{

    out<<widgetimpl.m_a<<" "<<widgetimpl.m_b<<" "<<widgetimpl.m_c;

    return out;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值