zoukankan      html  css  js  c++  java
  • <Effective C++>读书摘要--Designs and Declarations<一>

    <Item 18> Make interfaces easy to use correctly and hard to use incorrectly

    1、That being the case, if they use one incorrectly, your interface is at least partially to blame. Ideally, if an attempted use of an interface won't do what the client expects, the code won't compile; and if the code does compile, it will do what the client wants.Developing interfaces that are easy to use correctly and hard to use incorrectly requires that you consider the kinds of mistakes that clients might make.

    2、Many client errors can be prevented by the introduction of new types. Indeed, the type system is your primary ally in preventing undesirable code from compiling.如下可以防止类型参数使用错误

    struct Day {            struct Month {                struct Year {
      explicit Day(int d)     explicit Month(int m)         explicit Year(int y)
      :val(d) {}              :val(m) {}                    :val(y){}
    int val; int val; int val; }; }; };
    class Date { public: Date(const Month& m, const Day& d, const Year& y); ... }; Date d(30, 3, 1995); // error! wrong types Date d(Day(30), Month(3), Year(1995)); // error! wrong types Date d(Month(3), Day(30), Year(1995)); // okay, types are correct

    进一步可以如下编码防止月份范围使用错误。不使用enum,因为enum类是整数,不能防止类型范围使用错误,使用静态函数不使用常量,是保证变量有效的初始化。

    class Month {
    public:
      static Month Jan() { return Month(1); }   // functions returning all valid
      static Month Feb() { return Month(2); }   // Month values; see below for
      ...                                       // why these are functions, not
      static Month Dec() { return Month(12); }  // objects
      ...                                       // other member functions
    
    private:
      explicit Month(int m);                    // prevent creation of new
                                                // Month values
      ...                                       // month-specific data
    };
    
    Date d(Month::Mar(), Day(30), Year(1995));

    3、Another way to prevent likely client errors is to restrict what can be done with a type. A common way to impose restrictions is to add const. For example, Item 3 explains how const-qualifying the return type from operator* can prevent clients from making this error for user-defined types:

    if (a * b = c) ...                    // oops, meant to do a comparison!

    4、一个一般化的指导方针:unless there's a good reason not to, have your types behave consistently with the built-in types. Clients already know how types like int behave, so you should strive to have your types behave the same way whenever reasonable. For example, assignment to a*b isn't legal if a and b are ints, so unless there's a good reason to diverge from this behavior, it should be illegal for your types, too. When in doubt, do as the ints do.The interfaces to STL containers are largely (though not perfectly) consistent, and this helps make them fairly easy to use. For example, every STL container has a member function named size that tells how many objects are in the container. Contrast this with Java, where you use the length property for arrays, the length method for Strings, and the size method for Lists; and with .NET, where Arrays have a property named Length, while ArrayLists have a property named Count

    5、Any interface that requires that clients remember to do something is prone to incorrect use, because clients can forget to do it.直接返回std::tr1::shared_ptr<Investment> 可以避免资源忘记释放,同时可以在createInvestment中指定使用的deleter函数,当需要使用特定的函数释放资源的时候防止用户忘记指定特定的delete函数。

    Investment* createInvestment();   // from Item 13; parameters omitted
                                      // for simplicity
    std::tr1::shared_ptr<Investment> createInvestment();    //这种方法更好一些

    6、创建空的std::tr1::shared_ptr<Investment> 方法如下

    std::tr1::shared_ptr<Investment>      // attempt to create a null
      pInv(0, getRidOfInvestment);        // shared_ptr with a custom deleter;
                                          // this won't compile

    std::tr1::shared_ptr<Investment> // create a null shared_ptr with pInv(static_cast<Investment*>(0), // getRidOfInvestment as its getRidOfInvestment); // deleter; see Item 27 for info on // static_cast std::tr1::shared_ptr<Investment> createInvestment() { std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0), getRidOfInvestment); retVal = ... ; // make retVal point to the // correct object return retVal; }

    Of course, if the raw pointer to be managed by pInv could be determined prior to creating pInv, it would be better to pass the raw pointer to pInv's constructor instead of initializing pInv to null and then making an assignment to it. For details on why, consult Item 26.

    7、An especially nice feature of tr1::shared_ptr is that it automatically uses its per-pointer deleter to eliminate another potential client error, the "cross-DLL problem." This problem crops up when an object is created using new in one dynamically linked library (DLL) but is deleted in a different DLL. On many platforms, such cross-DLL new/delete pairs lead to runtime errors. tr1::shared_ptr avoids the problem, because its default deleter uses delete from the same DLL where the tr1::shared_ptr is created.

    8、The most common implementation of tr1::shared_ptr comes from Boost (see Item 55). Boost's shared_ptr is twice the size of a raw pointer, uses dynamically allocated memory for bookkeeping and deleter-specific data, uses a virtual function call when invoking its deleter, and incurs thread synchronization overhead when modifying the reference count in an application it believes is multithreaded. (You can disable multithreading support by defining a preprocessor symbol.) In short, it's bigger than a raw pointer, slower than a raw pointer, and uses auxiliary dynamic memory. In many applications, these additional runtime costs will be unnoticeable, but the reduction in client errors will be apparent to everyone.

    9、Things to Remember

    • Good interfaces are easy to use correctly and hard to use incorrectly. Your should strive for these characteristics in all your interfaces.

    • Ways to facilitate correct use include consistency in interfaces and behavioral compatibility with built-in types.

    • Ways to prevent errors include creating new types, restricting operations on types, constraining object values, and eliminating client resource management responsibilities.

    • TR1::shared_ptr supports custom deleters. This prevents the cross-DLL problem, can be used to automatically unlock mutexes (see Item 14), etc.

    <Item 19>Treat class design as type design

    10、You should therefore approach class design with the same care that language designers lavish on the design of the language's built-in types.Designing good classes is challenging because designing good types is challenging. Good types have a natural syntax, intuitive semantics, and one or more efficient implementations.

    11、How, then, do you design effective classes? First, you must understand the issues you face. Virtually every class requires that you confront the following questions, the answers to which often lead to constraints on your design:

    • How should objects of your new type be created and destroyed? How this is done influences the design of your class's constructors and destructor, as well as its memory allocation and deallocation functions (operator new, operator new[], operator delete, and operator delete[] — see Chapter 8), if you write them.

    • How should object initialization differ from object assignment? The answer to this question determines the behavior of and the differences between your constructors and your assignment operators. It's important not to confuse initialization with assignment, because they correspond to different function calls (see Item 4).

    • What does it mean for objects of your new type to be passed by value? Remember, the copy constructor defines how pass-by-value is implemented for a type.

    • What are the restrictions on legal values for your new type? Usually, only some combinations of values for a class's data members are valid. Those combinations determine the invariants your class will have to maintain. The invariants determine the error checking you'll have to do inside your member functions, especially your constructors, assignment operators, and "setter" functions. It may also affect the exceptions your functions throw and, on the off chance you use them, your functions' exception specifications.

    • Does your new type fit into an inheritance graph? If you inherit from existing classes, you are constrained by the design of those classes, particularly by whether their functions are virtual or non-virtual (see Items 34 and 36). If you wish to allow other classes to inherit from your class, that affects whether the functions you declare are virtual, especially your destructor (see Item 7).

    • What kind of type conversions are allowed for your new type? Your type exists in a sea of other types, so should there be conversions between your type and other types? If you wish to allow objects of type T1 to be implicitly converted into objects of type T2, you will want to write either a type conversion function in class T1 (e.g., operator T2) or a non-explicit constructor in class T2 that can be called with a single argument. If you wish to allow explicit conversions only, you'll want to write functions to perform the conversions, but you'll need to avoid making them type conversion operators or non-explicit constructors that can be called with one argument. (For an example of both implicit and explicit conversion functions, see Item 15.)

    • What operators and functions make sense for the new type? The answer to this question determines which functions you'll declare for your class. Some functions will be member functions, but some will not (see Items 23, 24, and 46).

    • What standard functions should be disallowed? Those are the ones you'll need to declare private (see Item 6).

    • Who should have access to the members of your new type? This question helps you determine which members are public, which are protected, and which are private. It also helps you determine which classes and/or functions should be friends, as well as whether it makes sense to nest one class inside another.

    • What is the "undeclared interface" of your new type? What kind of guarantees does it offer with respect to performance, exception safety (see Item 29), and resource usage (e.g., locks and dynamic memory)? The guarantees you offer in these areas will impose constraints on your class implementation.

    • How general is your new type? Perhaps you're not really defining a new type. Perhaps you're defining a whole family of types. If so, you don't want to define a new class, you want to define a new class template.
    • Is a new type really what you need? If you're defining a new derived class only so you can add functionality to an existing class, perhaps you'd better achieve your goals by simply defining one or more non-member functions or templates.

     These questions are difficult to answer, so defining effective classes can be challenging. Done well, however, user-defined classes in C++ yield types that are at least as good as the built-in types, and that makes all the effort worthwhile.

    12、Things to Remember

    • Class design is type design. Before defining a new type, be sure to consider all the issues discussed in this Item.

  • 相关阅读:
    游戏与微博的结合,一个微博后台与前端的设计(基于mysql)。(一)
    UDK 学习点滴 (不断更新)
    多语言版本与UI的展现问题
    用dx11检查你的硬件设备中有几个适配器(adapter)
    多重采样与dx11检查硬件多重采样能力的API
    不均匀点香求时间的问题解
    图片内包含文本制作方法
    vim 搜索 向上 向下 取消高亮
    解决Ucenter 头像上传小收获
    不知道是什么意思
  • 原文地址:https://www.cnblogs.com/lshs/p/4433498.html
Copyright © 2011-2022 走看看