<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 ...
随机推荐
- php的基础知识(四)
14.数组: 索引数组: 下标就是数字开始的. $arr = ['a','b','c',1,2,3]; 关联数组: $arr = [ 'a' => 'b', 'c' => 'd'; 'e' ...
- consonant_爆破音
consonant_爆破音_[p]和[b].[t]和[d].[k]和[g] 声带震动:发音的重要作用. 爆破音:发音在一瞬间,不会延长气流. [p]:声带不震动,嘴唇咬在一起,有明显的气流.map.p ...
- centos7 杂记
yum 源 https://www.cnblogs.com/renpingsheng/p/7845096.html 安装nginx php mysql https://www.cnblogs.com/ ...
- QOS-policy配置
QOS-QOS-policy配置 2018年7月7日 20:29 配置: 先匹配acl: [RT2]acl number 3000 [RT2-acl-adv-3000]description QOS ...
- R语言爬虫:CSS方法与XPath方法对比(代码实现)
CSS选择器和XPath方法都是用来定位DOM树的标签,只不过两者的定位表示形式上存在一些差别: CSS 方法提取节点 library("rvest") single_table_ ...
- ACM数论-快速幂
ACM数论——快速幂 快速幂定义: 顾名思义,快速幂就是快速算底数的n次幂.其时间复杂度为 O(log₂N), 与朴素的O(N)相比效率有了极大的提高. 原理: 以下以求a的b次方来介绍: 把b转换成 ...
- 汇编指令lodsb和stosb、lodsd和stosd
lodsb指令,将esi指向的地址处的数据取出来赋给AL寄存器,esi=esi+1: lodsw指令则取得是一个字. lodsd指令,取得是双字节,即mov eax,[esi],esi=esi+4: ...
- spring boot 数据库连接
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/jdjk?serverTimezone=Asia/Sha ...
- docker 应用场景
内容来自知乎.先mark,后续再研究 0.无痛尝试新事物 这应该是最早让我感受到docker的便利性的使用场景了. 以前,如果想尝试新的编程语言/数据库/命令行工具,会先找找apt的源里有没有相应的包 ...
- FCL中你不得不知的几种委托
FCL中丰富的类库信息极大的方便了我们的编码,很多我们日常经常用到的类型,FCL中已经帮我们定义好,下面要介绍的就是FCL中定义好的几种委托类型,直接使用它们不仅能提高我们的编码效率,而且还能让我们的 ...