zoukankan      html  css  js  c++  java
  • Effective C++ 笔记 —— Item 18: Make interfaces easy to use correctly and hard to use incorrectly.

    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. For example, suppose you’re designing the constructor for a class representing dates in time:

    class Date 
    {
    public:
        Date(int month, int day, int year);
        // ...
    };

    The way to prevent likely client errors is:

    class Month 
    {
    public:
        static Month Jan() { return Month(1); } // functions returning all valid Month values
        static Month Feb() { return Month(2); } //  see below for why these are functions, not objects 
        // ... 
        static Month Dec() { return Month(12); } 
        // ... 
        // 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));

    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.

    Another general guideline for making types easy to use correctly and hard to use incorrectly:

    unless there’s a good reason not to, have your types behave consistently with the built-in types.

    Any interface that requires that clients remember to do something is prone to incorrect use, because clients can forget to do it. For example, Item 13 introduces a factory function that returns pointers to dynamically allocated objects in an Investment hierarchy:

    Investment* createInvestment(); // from Item 13; parameters omitted for simplicity

    But what if clients forget to use the smart pointer? In many cases, a better interface decision would be to preempt the problem by having the factory function return a smart pointer in the first place:

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

    Suppose clients who get an Investment* pointer from createInvestment are expected to pass that pointer to a function called getRidOfInvestment instead of using delete on it. Such an interface would open the door to a new kind of client error, one where clients use the wrong resource-destruction mechanism (i.e., delete instead of getRidOfInvestment). The implementer of createInvestment can forestall such problems by returning a tr1::shared_ptr with getRidOfInvestment bound to it as its deleter.

    This means that the code for implementing createInvestment to return a tr1::shared_ptr with getRidOfInvestment as its deleter would look something like this:

    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;
    }

    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.

    For example, that if Stock is a class derived from Investment and createInvestment is implemented like this:

    std::tr1::shared_ptr<Investment> createInvestment()
    {
        return std::tr1::shared_ptr<Investment>(new Stock);
    }

    The returned tr1::shared_ptr can be passed among DLLs without concern for the cross-DLL problem. The tr1::shared_ptrs pointing to the Stock keep track of which DLL’s delete should be used when the reference count for the Stock becomes zero.

    tr1::shared_ptr is such an easy way to eliminate some client errors, it's worth an overview of the cost of using it. 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.

    Things to Remember

    • Good interfaces are easy to use correctly and hard to use incorrectly. You 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 crossDLL problem, can be used to automatically unlock mutexes (see Item 14), etc.
  • 相关阅读:
    wonderful life
    .net 4.5 webform 提取ModelState错误信息
    asp.net core Theme 中间件
    asp.net mvc 动态编译生成Controller
    asp.net mvc中换肤机制类库 ThemedViewEngines
    vs code 添加jquery的智能提示
    vs2017 iisexpress 绑定自定义域名
    asp.net Mvc 动态创建Controller
    查询Windows api
    预先创建占用一定磁盘空间的文件(使用C#)
  • 原文地址:https://www.cnblogs.com/zoneofmine/p/15237187.html
Copyright © 2011-2022 走看看