Effective C++ 笔记 —— Item 18: Make interfaces easy to use correctly and hard to use incorrectly.
2021/9/7 11:06:10
本文主要是介绍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.
这篇关于Effective C++ 笔记 —— Item 18: Make interfaces easy to use correctly and hard to use incorrectly.的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享