<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 ...
随机推荐
- 使用Letsencrypt做SSL certificate
为什么要使用Letsencrypt做SSL certificate? 最简单直接的原因是免费.但是免费存在是否靠谱的问题,尤其是对安全要求比较高的网站,需要考虑使用letsencrypt的安全性是否符 ...
- php5.4以上 mysqli 实例操作mysql 增,删,改,查
<?php //php5.4以上 mysqli 实例操作mysql header("Content-type:text/html;charset=utf8"); $conn ...
- mysql的length与char_length的区别
length: 是计算字段的长度一个汉字是算三个字符,一个数字或字母算一个字符 char_length:不管汉字还是数字或者是字母都算是一个字符 同时这两个函数,可用于判断数据中是否有中文文字 例 ...
- PAT A1060 (Advanced Level) Practice
If a machine can save only 3 significant digits, the float numbers 12300 and 12358.9 are considered ...
- python 基础练习题, 陆续添加中
判定用户输入数字是否为闰年 闰年的定义:能够被4整除的年份 #input是自定义输入内容的函数 year = input("请输入年份数字:") #xxx.isdigit方法是检测 ...
- linux进程篇 (三) 进程间的通信3 IPC通信
3 IPC通信 用户空间 进程A <----无法通信----> 进程B -----------------|--------------------------------------|- ...
- Simple, Poetic, Pithy
源自:Rob Pike points out Simple, Poetic, Pithy Don't communicate by sharing memory, share memory by co ...
- ajax的相关知识总结
一.ajax的工作原理 a.创建XMLHttpRequeat对象 var xmlhttp; if (window.XMLHttpRequest) { // IE7+, Firefox, Chrome, ...
- react--基本用法
1.安装了babel 但是终端执行 babel src --out-dir build命令时说"babel:command is not found" 经百度,找到solution ...
- Linux学习-rsyslog.service :记录登录文件的服务
rsyslog.service 的配置文件:/etc/rsyslog.conf 我们现在知道 rsyslogd 可以负责主机产生的各个信息的登录,而这些信息本身是有『严重等级』之分的, 而且, 这些资 ...