《C++ API设计》章节试读

出版社:人民邮电出版社
出版日期:2013-8
ISBN:9787115322999
作者:[美] Martin Reddy
页数:380页

《C++ API设计》的笔记-第62页

c语言中本质上是只暴露了this指针,可以认为c语言中不需要这种技术,因为没有要求把希望隐藏的内容显式地声明在头文件里。

《C++ API设计》的笔记-第114页 - 模式

Pimpl Idiom-------为完全隐藏类private实现细节的技巧。其实就是在公开的头文件的类中声明个私有的实现类指针。类似于Qt中的UI文件生成的ui_X.h文件。这个.h文件就是实现UI的头文件。
Use the pimpl idiom to keep implementation details out of your public header files.
就是上图。
When using the pimpl idiom use a private nested implementation class. Only use a public nested Impl class (or a public non-nested class) if other classes or free functions in the .cpp must access Impl members.1. You can’t hide private virtual methods in the implementation class. These must appear in the public class so that any derived classes are able to override them.
2. You may need to add a pointer in the implementation class back to the public class so that the Impl class can call public methods. Although you could also pass the public class into the implementation class methods that need it.
拷贝语义
一般来说,编译器会自动生成拷贝构造函数,但是它只是浅拷贝,如果之前用了impl idiom的话,你拷贝了公有对象,那么两个公有对象的实现类的指针都会指向同一个实现对象,那么如果两个公有对象全部都析构删除的话,实现类的对象不可能删除两次!!,所以程序崩溃了。有两种方法解决以上问题:
Make your class uncopyable.----------可以使用boost的noncopyable,实现机制实际上就是,对于拷贝构造函数声明为私有就可以了。
Explicitly define the copy semantics. ----------If you do want your users to be able to copy your pimpledobjects, then you should declare and define your own copy constructor and assignment operator.These can then perform a deep copy of your object, that is, create a copy of the Impl object instead of just copying the pointer.
以下代码就是让类对象不可拷贝#include <string>
class AutoTimer
{
public:
explicit AutoTimer(const std::string &name);
AutoTimer();
private:
// Make this object be non-copyable
AutoTimer(const AutoTimer &);
const AutoTimer &operator ¼(const AutoTimer &);
class Impl;
Impl *mImpl;
};
Think about the copy semantics of your pimpl classes and consider using a smart pointer to manage initialization and destruction of the implementation pointer.
Pimpl Idiom的好处:
1.Information hiding.
2.Reduced coupling.
3.Faster compiles.
4.Greater binary compatibility.
5.Lazy Allocation.
Pimpl Idiom的坏处:
The primary disadvantage of the pimpl idiom is that you must now allocate and free an additional implementation object for every object that is created.
if you are concerned with the memory allocator performance, then you may consider using the “Fast Pimpl” idiom (Sutter, 1999) where you overload the new and delete operators for your Impl class to use a more efficient small-memory fixed-size allocator.
单例模式
A Singleton is a more elegant way to maintain global state, but you should always question whether you need global state.Declare the constructor, destructor, copy constructor, and assignment operator to be private (or protected) to enforce the Singleton property.
以下代码就是单例模式:
class Singleton
{
public:
static Singleton &GetInstance();
private:
Singleton();
Singleton();
Singleton(const Singleton &);
const Singleton &operator = (const Singleton &);
};
static Singleton &obj = Singleton::GetInstance();it would be dangerous to initialize our singleton using a non-local static variable.
所以要这么实现初始化:Singleton &Singleton::GetInstance()
{
static Singleton instance;
return instance;
}
当然,以上代码实现是非线程安全的,要让它线程安全:
Singleton &Singleton::GetInstance()
{
Mutex mutex;
ScopedLock(&mutex); // unlocks mutex on function exit
static Singleton instance;
return instance;
}
当然以上代码有个性能问题,就是每次调用获取实例的时候,Mutex都要检查,因为自旋锁需要一直等待,这样做可以更好:
A commonly proposed solution to optimize this kind of over aggressive locking behavior is to
use the Double Check Locking Pattern (DCLP)
Singleton &Singleton::GetInstance()
{
static Singleton *instance = NULL;
if (! instance) // check #1
{
Mutex mutex;
ScopedLock(&mutex);
if (! instance) // check #2
{
instance = new Singleton();
}
}
return *instance;
}
当然以上代码DLCP不能保证在所有编译器平台和处理器平台正常工作。
Creating a thread-safe Singleton in C++ is hard. Consider initializing it with a static constructor or an API initialization function.
单例模式VS依赖注入
Dependency injection is a technique where an object is passed into a class (injected) instead of having the class create and store the object itself.
以下是代码:
class MyClass
{
MyClass() :
mDatabase(new Database("mydb", "localhost", "user", "pass"))
{}
private:
Database *mDatabase;
};
以上代码有个问题就是万一DataBase的构造函数接口变化了,那么又难改了,从效率角度上来讲,每次创建MyClass都会创建DataBase的实例,可以这样改,用依赖注入:
class MyClass
{
MyClass(Database *db) :
mDatabase(db)
{}
private:
Database *mDatabase;
};Dependency injection makes it easier to test code that uses Singletons.
Dependency injection can therefore be viewed as a way to avoid the proliferation of singletons by encouraging interfaces that accept the single instance as an input rather than requesting it internally via a GetInstance() method. Consider using Monostate instead of Singleton if you don’t need lazy initialization of global data or if you want the singular nature of the class to be transparent.
There are several alternatives to the Singleton pattern, including dependency injection, the Monostate pattern, and use of a session context.
抽象工厂模型C++ 这个查百度 google就可以了。
Use Factory Methods to provide more powerful class construction semantics and to hide subclass details.
API包装模式---有三种,Proxy, Adapter, and Facade
Writing a wrapper interface that sits on top of another set of classes is a relatively common API
design task.
perhaps you are working with a large legacy code base and rather than rearchitecting all of that code you decide to design a new cleaner API that hides the underlying legacy code (Feathers, 2004). Or perhaps you have written a C++ API and need to expose a plain C
interface for certain clients. Or perhaps you have a third-party library dependency that you want your clients to be able to access but you don’t want to expose that library directly to them.
代理模型:
The Proxy design pattern (Figure 3.3) provides a one-to-one forwarding interface to another class: calling FunctionA() in the proxy class will cause it to call FunctionA() in the original class. A Proxy provides an interface that forwards function calls to another interface of the same form.
适配器模型
The Adapter design pattern (Figure 3.4) translates the interface for one class into a compatible but different interface.
外观模型
The Fac¸ade design pattern (Figure 3.5) presents a simplified interface for a larger collection of classes. In effect, it defines a higher-level interface that makes the underlying subsystem easier to use.
观察者模型
An Observer lets you decouple components and avoid cyclic dependencies.
MVC模型
The MVC architectural pattern promotes the separation of core business logic, or the Model, from the user interface, or View. It also isolates the Controller logic that affects changes in the Model and updates the View.
以上的模型建立google找例子

《C++ API设计》的笔记-第273页

自动化测试工具

《C++ API设计》的笔记-第120页

API设计:最小完备性,正交性

《C++ API设计》的笔记-第160页 - 设计

Functional requirements specify how your API should behave.Use cases describe the requirements for your API from the perspective of the user.Use cases can be simple lists of short goal-oriented descriptions or can be more formal structured specifications that follow a prescribed template.User stories are a way to capture minimal requirements from users within an agile development process.
API设计
API design involves developing a top-level architecture and a detailed class hierarchy.
架构设计
Architecture design is constrained by a multitude of unique organizational, environmental, and operational factors.Always design for change. Change is inevitable.
提取关键对象
Identifying the key objects for an API is difficult. Try looking at the problem from different perspectives and keep iterating and refining your model.
架构模式
Avoid cyclic dependencies between components of your API.
与架构交流
Describe the high-level architecture and design rationale for your API in the accompanying user documentation.
类设计
Focus on designing 20% of the classes that define 80% of your API’s functionality.
面向对象概念
使用继承
Avoid deep inheritance hierarchies.Avoid multiple inheritance, except for interfaces and mixin classes.The LSP states that it should always be possible to substitute a base class for a derived class without any change in behavior.Prefer composition to inheritance.Your API should be closed to incompatible changes in its interface, but open to extensibility of its functionality.The Law of Demeter (LoD) states that you should only call functions in your own class or on immediately related objects.
错误处理
1.返回错误码----一个数值,win32 API就是这样设计
2.抛异常
3.终止程序Use a consistent and well-documented error handling mechanism.Derive your own exceptions from std::exception.Fail quickly and cleanly with accurate and thorough diagnostic details.

《C++ API设计》的笔记-第74页 - 质量

松耦合
Good APIs exhibit loose coupling and high cohesion.
如下使用类前置声明就有效降低了类MyObject和类MyObjectHolder的耦合性
class MyObject; // only need to know the name of MyObject
class MyObjectHolder
{
public:
MyObjectHolder();
void SetObject(MyObject *obj);
MyObject *GetObject() const;
private:
MyObject *mObj;
};In this case, if the associated .cpp file simply stores and returns the MyObject pointer, and restricts any interaction with it to pointer comparisons, then it does not need to #include “MyObject.h” either. In that case, the MyObjectHolder class can be decoupled from the physical implementation of MyObject.Use a forward declaration for a class unless you actually need to #include its full definition.
降低类的耦合性
whenever you have a choice, you should prefer declaring a function as a non-member non-friend function rather than as a member function 。Doing so
improves encapsulation and also reduces the degree of coupling of those functions to the class.Prefer using non-member non-friend functions instead of member functions to reduce coupling.
例子如下:
// myobject.h
class MyObject
{
public:
void PrintName() const;
std::string GetName() const;
. . .
protected:
. . .
private:
std::string mName;
. . .
};
如下改更好:
// myobject.h
class MyObject
{
public:
std::string GetName() const;
. . .
protected:
. . .
private:
std::string mName;
. . .
};
void PrintName(const MyObject &obj);Data redundancy can sometimes be justified to reduce coupling between classes.Manager classes can reduce coupling by encapsulating several lower-level classes.
回调,观察者和消息通知
reduce coupling within an API relates to the problem of notifying other classes when some event occurs.
这里可以看《设计模式》

《C++ API设计》的笔记-第48页 - 质量

使用虚函数时需要注意的:
Virtual function calls must be resolved at run time by performing a vtable lookup, whereas nonvirtual function calls can be resolved at compile time. This can make virtual function calls slower
than non-virtual calls. In reality, this overhead may be negligible, particularly if your function
does non-trivial work or if it is not called frequently.
• The use of virtual functions increases the size of an object, typically by the size of a pointer to the
vtable. This may be an issue if you wish to create a small object that requires a very large number
of instances. Again, in practice this will likely be insignificant when compared to the amount of
memory consumed by your various member variables.
• Adding, reordering, or removing a virtual function will break binary compatibility. This is because a
virtual function call is typically represented as an integer offset into the vtable for the class. Therefore, changing its order or causing the order of any other virtual functions to change means that existing code will need to be recompiled to ensure that it still calls the right functions.
• Virtual functions cannot always be inlined. You may reasonably think that it does not make sense
to declare a virtual as inline at all because virtual functions are resolved at run time, whereas
inlining is a compile-time optimization. However, there are certain constrained situations where
a compiler can inline a virtual function. All the same, these are far fewer than the cases where a
non-virtual function can be inlined. (Remember that the inline keyword in C++ is merely a hint
to the compiler.)
Overloading is tricky with virtual functions. A symbol declared in a derived class will hide all
symbols with the same name in the base class. Therefore, a set of overloaded virtual functions
in a base class will be hidden by a single overriding function in a subclass. There are ways to
get around this (Dewhurst, 2002), but it’s better to simply avoid overloading virtual functions.Avoid declaring functions as overridable (virtual) until you have a valid and compelling need to do so.Ultimately, you should only allow overriding if you explicitly intend for this to be possible. A class with no virtual functions tends to be more robust and requires less maintenance than one with virtual functions. As a general rule of thumb, if your API does not call a particular method internally,then that method probably should not be virtual. You should also only allow subclassing in situations where it makes sense: where the potential subclasses form an “is-a” relationship with the base class.
如果你想让你设计的类可以子类化,请遵守以下原则:
1. Always declare your destructor to be virtual if there are any virtual functions in your class. This is so that subclasses can free any extra resources that they may have allocated.
2. Always document how methods of your class call each other. If a client wants to provide an alternative implementation for a virtual function, they will need to know which other methods need to be called within their implementation in order to preserve the internal integrity of the object.
3. Never call virtual functions from your constructor or destructor. These calls will never be directed to a subclass (Meyers, 2005). This does not affect the appearance of your API, but it’s a good rule to know all the same.

《C++ API设计》的笔记-第205页

性能分析工具

《C++ API设计》的笔记-第44页 - 质量

隐藏实现方法
Never return non-const pointers or references to private data members. This breaks encapsulation.
其实就是实现类公有接口的一些方法设置为private,或者把它们隐藏起来,不然API的用户可能会修改类的内部状态,也不要为一些关键的成员变量设置Get函数,导致可以随意操作类内部的状态。
考虑一段代码
#include <string>
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
class URLDownloader
{
public:
URLDownloader();
bool DownloadToFile(const std::string &url,
const std::string &localFile);
bool SocketConnect(const std::string &host, int port);
void SocketDisconnect();
bool IsSocketConnected() const;
int GetSocket() const;
bool SocketWrite(const char *buffer, size_t bytes);
size_t SocketRead(char *buffer, size_t bytes);
bool WriteBufferToFile(char *buffer,
const std::string &filename);
private:
int mSocketID;
struct sockaddr_in mServAddr;
bool mIsConnected;
};Obviously a better design for our URLDownloader class would be to make every method private,except for the constructor and the DownloadToFile() method. Everything else is implementation detail. You are then free to change the implementation without affecting any clients of the class.
There is still something very unsatisfying about this situation though. I have hidden the implementation details from the compiler’s point of view, but a person can still look at the header file and see all of the internal details for your class. In fact, this is very likely because you must distribute the header file to allow clients to compile their code against your API. Also, you must #include all header files needed for the private members of your class, even though they are not dependencies of the public interface. For example, the URLDownloader header needs to #include all of the platform-specific socket headers.This is an unfortunate limitation of the C++ language: all public, protected, and private members of
a class must appear in the declaration for that class. Ideally, the header for our class would look like
#include <string>
class URLDownloader
{
public:
URLDownloader();
bool DownloadToFile(const std::string &url,
const std::string &localFile);
};Then all of the private members could be declared somewhere else, such as in the .cpp file. However, this is not possible with C++ (the reason is so that the size of all objects can be known at compile time). Nevertheless, there are still ways to hide private members from your public header files (Headington, 1995). One popular technique is called the Pimpl idiom. This involves isolating all of a class’s private data members inside of a separate implementation class or struct in the .cpp file.
The .h file then only needs to contain an opaque pointer to this implementation class.
要隐藏private成员方法推荐用---Pimpl idiom ,当然也可以在.cpp实现文件中以static函数定义实现它。因为我不想让用户知道private中包含哪些私有实现方法
Prefer declaring private functionality as static functions within the .cpp file rather than exposing them in public headers as private methods. (Using the Pimpl idiom is even better though.)
以上提到的 Pimpl idiom,我上网查了下,其实Qt的UI Designer生成的ui_XX.h就是这样的,就实现UI与逻辑的高度分离,只提供了一个ui指针

《C++ API设计》的笔记-第46页 - 质量

隐藏实现类
实现类就是实现一个类公有方法的功能的类,它应该是对用户不可见的(即使在头文件里),用户不应该知道它的存在。
像以下的代码设计就不好,因为实现类暴露给用户了,即使用户不能使用它(因为是private)的。
#include <vector>
class Fireworks
{
public:
Fireworks();
void SetOrigin(double x, double y);
void SetColor(float r, float g, float b);
void SetGravity(float g);
void SetSpeed(float s);
void SetNumberOfParticles(int num);
void Start();
void Stop();
void NextFrame(float dt);
private:
class FireParticle
{
public:
double mX, mY;
double mVelocityX, mVelocityY;
double mAccelerationX, mAccelerationY;
double mLifeTime;
};
double mOriginX, mOriginY;
float mRed, mGreen, mBlue;
float mGravity;
float mSpeed;
bool mIsActive;
std::vector<FireParticle *> mParticles;
};
最小化API,一个函数只做好一件事。
Remember Occam’s razor: “pluralitas non-est ponenda sine necessitate” (plurality should not be posited without
necessity).
以上是奥科姆剃刀:若无必要,勿增实体。
you should try to keep your APIs as simple as you can: minimize the number of classes you expose and the number of public members in those classes. As a bonus, this will also make your API easier to understand, easier for your users to keep a mental model of the API in their heads, and easier for you to debug.When in doubt, leave it out! Minimize the number of public classes and functions in your API.

《C++ API设计》的笔记-第42页 - 隐藏实现细节

设计API的要素
1.模型化关键对象----就是一个类接口hold哪些对象
2.隐藏细节----分为物理隐藏和逻辑隐藏
物理隐藏------------就是.h和.cpp文件,接口声明和定义(实现)分开。理想的状态下就是,.h文件不暴露任何实现细节。如果.h文件里面有函数实现,说明意图是要让编译器内联它,但是这也是糟糕的设计,要避免
逻辑隐藏(封装)----利用语言提供特性,来限定访问权限,C++ 就是类的private,protected,public。
为一个类提供getter和setter有哪些好处?成员变量最好不要设置为public和protected,因为protected会让该类的子类有访问权限,这样跟public没太大区别了。
Data members of a class should always be declared private, never public or protected.Validation.------You can perform validation on the values to ensure that the internal state of the class is always valid and consistent. For example, if you have a method that lets clients set a new RGB color, you could check that each of the supplied red, green, and blue values are within the valid range, for example, 0 to 255 or 0.0 to 1.0.
• Lazy evaluation-------Calculating the value of a variable may incur a significant cost, which youwould prefer to avoid until necessary. By using a getter method to access the underlying data value, you can defer the costly calculation until the value is actually requested.
• Caching.--------A classic optimization technique is to store the value of a frequently requested calculation and then directly return that value for future requests. For example, a machine’s total memory size can be found on Linux by parsing the /proc/meminfo file. Instead of performing a file read for every request to find the total memory size, it would be better to cache the result after
the first read and then simply return that cached value for future requests.
• Extra computation---------If necessary, you can perform additional operations whenever the client tries to access avariable.Forexample,perhapsyoualwayswanttowritethecurrentstateofaUserPreferences object to a configuration file on disk whenever the user changes the value of a preference setting.
• Notifications.--------Other modules may wish to know when a value has changed in your class. For example, if you are implementing a data model for a progress bar, the user interface code will want to know when the progress value has been updated so that it can update the GUI. You might therefore wish to issue a change notification as part of a setter method.
• Debugging.------You may want to add debugging or logging statements so that you can track when variables are accessed or changed by clients or you may wish to add assert statements to enforce assumptions.
Synchronization.-------You may release the first version of your API and then later find that you need to make it thread safe. The standard way to do this is to add mutex locking whenever a value is accessed.This would only be possible if you have wrapped access to the data values in getter/setter methods.
Finer access control.--------If you make a member variable public, then clients can read and write that value as they wish. However, by using getter/setter methods, you can provide a finer level of read/write control. For example, you can make the value be read-only by not providing a setter method.

Maintaining invariant relationships------Some internal data values may depend on each other. For example, in a car animation system you may calculate the velocity and acceleration of the car based on the time it takes to travel between key frames. You can calculate velocity based on the change in position over time, and acceleration based on the change in velocity over time. However, if a client can access your internal state for this calculation, they could change the acceleration
value so that it does not correlate to the car’s velocity, thus producing unexpected results.
另外一个值得讨论的一点就是暴露成员变量,有一种可能是为了性能原因。因为函数的执行需要栈,这样在性能要求苛刻的程序如果有很多对象,那么直接访问公有成员变量速度可能比通过getter和setter方法快2-3倍。
但是即使是这样的情况,也不要暴露成员变量。原因如下:
First of all, the overhead
of a method call will very likely be insignificant for practically all of your API calls. Even if you are writing performance-critical APIs, the careful use of inlining, combined with a modern optimizing compiler, will normally completely eradicate the method call overhead, giving you all the performance benefits of directly exposing member variables. If you’re still concerned, try timing your API with inlined getter/setters and then with public member variables.

《C++ API设计》的笔记-第63页 - 质量

方便的API
也就是Core API应该是功能(参数)最小化的,只提供基本的操作。在Core层上在wrap一层以方便用户使用。
your API should present the basic functionality via an easy-to-use interface while reserving advanced functionality for a separate layerAdd convenience APIs as separate modules or libraries that sit on top of your minimal core API.
易于使用
Avoiding the use of abbreviations can also play a factor in discoverabilityA good API, in addition to being easy to use, should also be difficult to misuse. Prefer enums to booleans to improve code readability.Avoid functions with multiple parameters of the same type.Use consistent function naming and parameter ordering.An orthogonal API means that functions do not have side effects.
Two important factors to remember for designing orthogonal APIs are as follow.
1. Reduce redundancy. Ensure that the same information is not represented in more than one way.There should be a single authoritative source for each piece of knowledge.
2. Increase independence. Ensure that there is no overlapping of meaning in the concepts that are exposed. Any overlapping concepts should be decomposed into their basal components.
以上的减小冗余其实也是DRY原则所说的。代码抽象的是逻辑,静止简单的复制粘贴。
健壮的资源分配
一般C++的BUG会始于以下几种:
Null dereferencing: trying to use -> or * operators on a NULL pointer.
Double freeing: calling delete or free() on a block of memory twice.
• Accessing invalid memory: trying to use -> or * operators on a pointer that has not been allocated yet or that has been freed already.
• Mixing Allocators: using delete to free memory that was allocated with malloc() or using free() to return memory allocated with new.
• Incorrect array deallocation: using the delete operator instead of delete [] to free an array.
• Memory leaks: not freeing a block of memory when you are finished with it.
可以用智能指针避免以上的情况
1. Shared pointers. These are reference-counted pointers where the reference count can be incremented by one when a piece of code wants to hold onto the pointer and decremented by one when it is finished using the pointer. When the reference count reaches zero, the object pointed to by the pointer is automatically freed. This kind of pointer can help avoid the problems of accessing freed memory by ensuring that the pointer remains valid for the period that you wish to use it.
2. Weak pointers. A weak pointer contains a pointer to an object, normally a shared pointer, but does not contribute to the reference count for that object. If you have a shared pointer and a weak pointer referencing the same object, and the shared pointer is destroyed, the weak pointer immediately becomes NULL. In this way, weak pointers can detect whether the object being pointed to has expired: if the reference count for the object it is pointing to is zero. This helps avoid the dangling pointer problem where you can have a pointer that is referencing freed memory.
3. Scoped pointers. These pointers support ownership of single objects and automatically deallocate their objects when the pointer goes out of scope. They are sometimes also called auto pointers. Scoped pointers are defined as owning a single object and as such cannot be copied.Return a dynamically allocated object using a smart pointer if the client is responsible for deallocating it.Think of resource allocation and deallocation as object construction and destruction.
平台独立性
Never put platform-specific #if or #ifdef statements into your public APIs. It exposes implementation details and makes your API appear different on different platforms.
以上就说是说,不要在公开的API中使用跨平台的预编译指令。在公开头文件中是不友好的做法。
比如以下代码是不合适的:
class MobilePhone
{
public:
bool StartCall(const std::string &number);
bool EndCall();
#if defined TARGET_OS_IPHONE
bool GetGPSLocation(double &lat, double &lon);
#endif
};
以上代码中意图表示只有iPhone平台才有手机GPS定位的功能,其它没有,但是这个实现暴露出来到头文件中了。是不好的。如果后期还会有需要手机不同的平台加进来呢?
其实可以这样改:
class MobilePhone
{
public:
bool StartCall(const std::string &number);
bool EndCall();
bool HasGPS() const;
bool GetGPSLocation(double &lat, double &lon);
};
在对应的实现文件中:
bool MobilePhone::HasGPS() const
{
#if defined TARGET_OS_IPHONE
return true;
#else
return false;
#endif
}
这样对应的平台特定的预编译指令就隐藏在头文件中了,是提供一个平台独立的hasGPS的接口来做隔离的。


 C++ API设计下载 更多精彩书评


 

外国儿童文学,篆刻,百科,生物科学,科普,初中通用,育儿亲子,美容护肤PDF图书下载,。 零度图书网 

零度图书网 @ 2024