<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(, , ); // error! wrong types
Date d(Day(), Month(), Year()); // error! wrong types
Date d(Month(), Day(), Year()); // okay, types are correct
进一步可以如下编码防止月份范围使用错误。不使用enum,因为enum类是整数,不能防止类型范围使用错误,使用静态函数不使用常量,是保证变量有效的初始化。
class Month {
public:
static Month Jan() { return Month(); } // functions returning all valid
static Month Feb() { return Month(); } // Month values; see below for
... // why these are functions, not
static Month Dec() { return Month(); } // objects
... // other member functions
private:
explicit Month(int m); // prevent creation of new
// Month values
... // month-specific data
};
Date d(Month::Mar(), Day(), Year());
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(, 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*>(), // 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*>(),
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.
<Effective C++>读书摘要--Designs and Declarations<一>的更多相关文章
- <Effective C++>读书摘要--Designs and Declarations<二>
<Item 20> Prefer pass-by-reference-to-const to pass-by-value 1.By default, C++ passes objects ...
- <Effective C++>读书摘要--Designs and Declarations<三>
<Item 22> Declare data members private 1.使数据成员private,保持了语法的一致性,client不会为访问一个数据成员是否需要使用括号进行函数调 ...
- <Effective C++>读书摘要--Implementations<二>
<Item29> Strive for exception-safe code. 1.如下面的代码 class PrettyMenu { public: ... void changeBa ...
- <Effective C++>读书摘要--Inheritance and Object-Oriented Design<二>
<Item 36> Never redefine an inherited non-virtual function 1.如下代码通过不同指针调用同一个对象的同一个函数会产生不同的行为Th ...
- <Effective C++>读书摘要--Resource Management<二>
<Item 15> Provide access to raw resources in resource-managing classes 1.You need a way to con ...
- <Effective C++>读书摘要--Ctors、Dtors and Assignment Operators<二>
<Item 9> Never call virtual functions during construction or destruction 1.you shouldn't call ...
- <Effective C++>读书摘要--Templates and Generic Programming<一>
1.The initial motivation for C++ templates was straightforward: to make it possible to create type-s ...
- <Effective C++>读书摘要--Inheritance and Object-Oriented Design<一>
1.Furthermore, I explain what the different features in C++ really mean — what you are really expres ...
- <Effective C++>读书摘要--Implementations<一>
1.For the most part, coming up with appropriate definitions for your classes (and class templates) a ...
随机推荐
- 树莓派如何连接WIFI
hello,大家好,我是叶秋! 上一期呢,给大家介绍了如何给树莓派安装系统,有细心的朋友就会发现上一期安装系统的文章漏了一点点知识,不知道机智的你是否有发现呢~~(尴尬
- 深浅拷贝--python(预习中随手写的。因为当时很无聊。。。)
需要知识准备,pyhton基本常识,python的小数据池概念. 深浅拷贝操作需要模块导入:import copy emmm,对于python中的两种数据类型来说. 1.数字,字符串 2.列表,元祖, ...
- 优步UBER司机全国各地奖励政策汇总 (2月29日-3月6日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- 北京Uber优步司机奖励政策(2月28日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- 北京Uber优步司机奖励政策(1月7日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- Hive实现自增列
1.用row_number()函数生成代理键 ) max_id from id_test) t2; 2.用UDFRowSequence生成代理键 ——报错? add jar ...
- 几个常用的轻量级web服务
Node.js 安装:npm install http-server 使用:hs命令,可启动以当前目前为webroot的8080端口web服务,也可指定端口 Python 安装:内置 使用:pytho ...
- appium -- 页面出现弹窗,关闭后,无法识别页面元素
1. 问题:如图所示:在修改手势密码的过程中,点击了返回按钮后,弹出该弹窗:点击继续设置后,就发现 driver.getPageSource()获取不到页面元素.在找了一圈无用的资料后,没有什么好的处 ...
- Git一分钟系列--快速安装git客户端
在项目开发过程中,几乎所有公司都会用到版本控制工具来管理自己的项目资源文件,比如Git,SVN. 什么是svn? 版本控制软件,通过svn来实现版本控制首先需要搭建一个服务器,在服务器上创建仓库保存项 ...
- Ubuntu卡在logo界面
对于这个问题,我也是在最近一次偶然的机会中发现的. 我重装了了Ubuntu 18.04, 很多东西需要重新配置, 有个刚性需求就是配置shadowsocks实现***,对于从windows向linu ...