没有合适的资源?快使用搜索试试~ 我知道了~
首页C++设计模式与泛型编程实战
C++设计模式与泛型编程实战
需积分: 10 4 下载量 6 浏览量
更新于2024-07-23
收藏 1.92MB PDF 举报
"《Modern C++ Design》是一本由Andrei Alexandrescu编著的专业书籍,主要探讨了现代C++编程中的泛型编程和设计模式应用。本书由Addison Wesley出版,于2001年2月发行,共352页,ISBN号为0-201-70431-5。它面向两类读者群体:一是有经验的C++程序员,他们希望掌握最新的库编写技术;二是忙碌的程序员,他们需要在无需大量学习投入的情况下完成工作。"
在这本书中,Andrei Alexandrescu揭示了一种强大的新方法——“泛型模式”或“模式模板”,这为C++的设计提供了新的扩展方式,将模板和设计模式相结合,创造出可能之前未曾想到的代码结构。对于涉及C++设计和编码工作的读者来说,这本书是强烈推荐的,因为它能够帮助读者实现表达力强、灵活且高度可重用的代码。
作者Andrei Alexandrescu以其非凡的创造力和编程技艺,提出了一种前沿的设计方法,即泛型组件。这些组件是可重用的设计模板,能为编译器生成样板代码,从而降低了复杂性和开发成本。通过使用这些设计模式,程序员可以更有效地组织代码,提高软件的可维护性和可扩展性。
这本书不仅涵盖了C++的基础知识,还深入到高级主题,如类型安全、模板元编程、智能指针、以及如何利用C++的特性来实现设计模式。例如,书中可能会讨论如何使用模板来实现工厂模式、观察者模式等经典设计模式的泛型版本,以及如何利用模板元编程来在编译时进行计算和优化。
此外,Andrei Alexandrescu还可能讨论了C++标准库中的STL(Standard Template Library)是如何体现这些设计理念的,以及如何自定义容器和迭代器以适应特定的项目需求。他还可能阐述了如何利用C++的异常处理机制来增强程序的健壮性,以及如何通过RAII(Resource Acquisition Is Initialization)原则来管理资源,防止内存泄漏。
《Modern C++ Design》是一本深度和技术性并重的书籍,它不仅教会读者如何编写更高效的C++代码,更提供了对现代C++编程思想的深刻理解。无论你是C++的老手还是新手,都能从中受益匪浅,提升自己的编程技巧和设计能力。
![](https://csdnimg.cn/release/download_crawler_static/7285323/bg10.jpg)
2
Chapter 1. Policy-Based Class Design
This chapter describes policies and policy classes, important class design techniques that enable the
creation of flexible, highly reusable libraries—as Loki aims to be. In brief, policy-based class design
fosters assembling a class with complex behavior out of many little classes (called policies), each of which
takes care of only one behavioral or structural aspect. As the name suggests, a policy establishes an
interface pertaining to a specific issue. You can implement policies in various ways as long as you respect
the policy interface.
Because you can mix and match policies, you can achieve a combinatorial set of behaviors by using a
small core of elementary components.
Policies are used in many chapters of this book. The generic
SingletonHolder class template (Chapter
6) uses policies for managing lifetime and thread safety. SmartPtr (Chapter 7) is built almost entirely
from policies. The double-dispatch engine in Chapter 11
uses policies for selecting various trade-offs. The
generic Abstract Factory (Gamma et al. 1995
) implementation in Chapter 9 uses a policy for choosing a
creation method.
This chapter explains the problem that policies are intended to solve, provides details of policy-based class
design, and gives advice on decomposing a class into policies.
1.1 The Multiplicity of Software Design
Software engineering, maybe more than any other engineering discipline, exhibits a rich multiplicity: You
can do the same thing in many correct ways, and there are infinite nuances between right and wrong. Each
path opens up a new world. Once you choose a solution, a host of possible variants appears, on and on at
all levels—from the system architecture level down to the smallest coding detail. The design of a software
system is a choice of solutions out of a combinatorial solution space.
Let's think of a simple, low-level design artifact: a smart pointer (Chapter 7
). A smart pointer class can be
single threaded or multithreaded, can use various ownership strategies, can make various trade-offs
between safety and speed, and may or may not support automatic conversions to the underlying raw
pointer type. All these features can be combined freely, and usually exactly one solution is best suited for a
given area of your application.
The multiplicity of the design space constantly confuses apprentice designers. Given a software design
problem, what's a good solution to it? Events? Objects? Observers? Callbacks? Virtuals? Templates? Up to
a certain scale and level of detail, many different solutions seem to work equally well.
The most important difference between an expert software architect and a beginner is the knowledge of
what works and what doesn't. For any given architectural problem, there are many competing ways of
solving it. However, they scale differently and have distinct sets of advantages and disadvantages, which
may or may not be suitable for the problem at hand. A solution that appears to be acceptable on the
whiteboard might be unusable in practice.
Designing software systems is hard because it constantly asks you to choose. And in program design, just
as in life, choice is hard.
Good, seasoned designers know what choices will lead to a good design. For a beginner, each design
choice opens a door to the unknown. The experienced designer is like a good chess player: She can see
![](https://csdnimg.cn/release/download_crawler_static/7285323/bg11.jpg)
3
more moves ahead. This takes time to learn. Maybe this is the reason why programming genius may show
at an early age, whereas software design genius tends to take more time to ripen.
In addition to being a puzzle for beginners, the combinatorial nature of design decisions is a major source
of trouble for library writers. To implement a useful library of designs, the library designer must classify
and accommodate many typical situations, yet still leave the library open-ended so that the application
programmer can tailor it to the specific needs of a particular situation.
Indeed, how can one package flexible, sound design components in libraries? How can one let the user
configure these components? How does one fight the "evil multiplicity" of design with a reasonably sized
army of code? These are the questions that the remainder of this chapter, and ultimately this whole book,
tries to answer.
1.2 The Failure of the Do-It-All Interface
Implementing everything under the umbrella of a do-it-all interface is not a good solution, for several
reasons.
Some important negative consequences are intellectual overhead, sheer size, and inefficiency. Mammoth
classes are unsuccessful because they incur a big learning overhead, tend to be unnecessarily large, and
lead to code that's much slower than the equivalent handcrafted version.
But maybe the most important problem of an overly rich interface is loss of static type safety. One essential
purpose of the architecture of a system is to enforce certain axioms "by design"—for example, you cannot
create two Singleton objects (see Chapter 6
) or create objects of disjoint families (see Chapter 9). Ideally, a
design should enforce most constraints at compile time.
In a large, all-encompassing interface, it is very hard to enforce such constraints. Typically, once you have
chosen a certain set of design constraints, only certain subsets of the large interface remain semantically
valid. A gap grows between syntactically valid and semantically valid uses of the library. The programmer
can write an increasing number of constructs that are syntactically valid, but semantically illegal.
For example, consider the thread-safety aspect of implementing a Singleton object. If the library fully
encapsulates threading, then the user of a particular, nonportable threading system cannot use the Singleton
library. If the library gives access to the unprotected primitive functions, there is the risk that the
programmer will break the design by writing code that's syntactically—but not semantically—valid.
What if the library implements different design choices as different, smaller classes? Each class would
represent a specific canned design solution. In the smart pointer case, for example, you would expect a
battery of implementations:
SingleThreadedSmartPtr, MultiThreadedSmartPtr,
RefCountedSmartPtr, RefLinkedSmartPtr, and so on.
The problem that emerges with this second approach is the combinatorial explosion of the various design
choices. The four classes just mentioned lead necessarily to combinations such as
SingleThreadedRefCountedSmartPtr. Adding a third design option such as conversion support
leads to exponentially more combinations, which will eventually overwhelm both the implementer and the
user of the library. Clearly this is not the way to go. Never use brute force in fighting an exponential.
Not only does such a library incur an immense intellectual overhead, but it also is extremely rigid. The
slightest unpredicted customization—such as trying to initialize default-constructed smart pointers with a
particular value—renders all the carefully crafted library classes useless.
![](https://csdnimg.cn/release/download_crawler_static/7285323/bg12.jpg)
4
Designs enforce constraints; consequently, design-targeted libraries must help user-crafted designs to
enforce their own constraints, instead of enforcing predefined constraints. Canned design choices would be
as uncomfortable in design-targeted libraries as magic constants would be in regular code. Of course,
batteries of "most popular" or "recommended" canned solutions are welcome, as long as the client
programmer can change them if needed.
These issues have led to an unfortunate state of the art in the library space: Low-level general-purpose and
specialized libraries abound, while libraries that directly assist the design of an application—the higher-
level structures—are practically nonexistent. This situation is paradoxical because any nontrivial
application has a design, so a design-targeted library would apply to most applications.
Frameworks try to fill the gap here, but they tend to lock an application into a specific design rather than
help the user to choose and customize a design. If programmers need to implement an original design, they
have to start from first principles—classes, functions, and so on.
1.3 Multiple Inheritance to the Rescue?
A TemporarySecretary class inherits both the Secretary and the Temporary classes.
[1]
TemporarySecretary has the features of both a secretary and a temporary employee, and possibly
some more features of its own. This leads to the idea that multiple inheritance might help with handling the
combinatorial explosion of design choices through a small number of cleverly chosen base classes. In such
a setting, the user would build a multi-threaded, reference-counted smart pointer class by inheriting some
BaseSmartPtr class and two classes: MultiThreaded and RefCounted. Any experienced class
designer knows that such a naïve design does not work.
[1]
This example is drawn from an old argument that Bjarne Stroustrup made in favor of multiple inheritance,
in the first edition of The C++ Programming Language. At that time, multiple inheritance had not yet been
introduced in C++.
Analyzing the reasons why multiple inheritance fails to allow the creation of flexible designs provides
interesting ideas for reaching a sound solution. The problems with assembling separate features by using
multiple inheritance are as follows:
1. Mechanics. There is no boilerplate code to assemble the inherited components in a controlled
manner. The only tool that combines
BaseSmartPtr, MultiThreaded, and RefCounted is a
language mechanism called multiple inheritance. The language applies simple superposition in
combining the base classes and establishes a set of simple rules for accessing their members. This
is unacceptable except for the simplest cases. Most of the time, you need to carefully orchestrate
the workings of the inherited classes to obtain the desired behavior.
2. Type information. The base classes do not have enough type information to carry out their tasks.
For example, imagine you try to implement deep copy for your smart pointer class by deriving
from a
DeepCopy base class. What interface would DeepCopy have? It must create objects of a
type it doesn't know yet.
3. State manipulation. Various behavioral aspects implemented with base classes must manipulate
the same state. This means that they must use virtual inheritance to inherit a base class that holds
the state. This complicates the design and makes it more rigid because the premise was that user
classes inherit library classes, not vice versa.
Although combinatorial in nature, multiple inheritance by itself cannot address the multiplicity of design
choices.
![](https://csdnimg.cn/release/download_crawler_static/7285323/bg13.jpg)
5
1.4 The Benefit of Templates
Templates are a good candidate for coping with combinatorial behaviors because they generate code at
compile time based on the types provided by the user.
Class templates are customizable in ways not supported by regular classes. If you want to implement a
special case, you can specialize any member functions of a class template for a specific instantiation of the
class template. For example, if the template is
SmartPtr<T>, you can specialize any member function for,
say,
SmartPtr<Widget>. This gives you good granularity in customizing behavior.
Furthermore, for class templates with multiple parameters, you can use partial template specialization (as
you will see in Chapter 2
). Partial template specialization gives you the ability to specialize a class
template for only some of its arguments. For example, given the definition
template <class T, class U> class SmartPtr { ... };
you can specialize SmartPtr<T, U> for Widget and any other type using the following syntax:
template <class U> class SmartPtr<Widget, U> { ... };
The innate compile-time and combinatorial nature of templates makes them very attractive for creating
design pieces. As soon as you try to implement such designs, you stumble upon several problems that are
not self-evident:
1. You cannot specialize structure. Using templates alone, you cannot specialize the structure of a
class (its data members). You can specialize only functions.
2. Specialization of member functions does not scale. You can specialize any member function of a
class template with one template parameter, but you cannot specialize individual member
functions for templates with multiple template parameters. For example:
template <class T> class Widget
{
void Fun() { .. generic implementation ... }
};
// OK: specialization of a member function of Widget
template <> Widget<char>::Fun()
{
... specialized implementation ...
}
template <class T, class U> class Gadget
{
void Fun() { .. generic implementation ... }
};
// Error! Cannot partially specialize a member class of Gadget
template <class U> void Gadget<char, U>::Fun()
{
... specialized implementation ...
}
3. The library writer cannot provide multiple default values. At best, a class template implementer
can provide a single default implementation for each member function. You cannot provide
several defaults for a template member function.
Now compare the list of drawbacks of multiple inheritance with the list of drawbacks of templates.
Interestingly, multiple inheritance and templates foster complementary trade-offs. Multiple inheritance has
scarce mechanics; templates have rich mechanics. Multiple inheritance loses type information, which
![](https://csdnimg.cn/release/download_crawler_static/7285323/bg14.jpg)
6
abounds in templates. Specialization of templates does not scale, but multiple inheritance scales quite
nicely. You can provide only one default for a template member function, but you can write an unbounded
number of base classes.
This analysis suggests that a combination of templates and multiple inheritance could engender a very
flexible device, appropriate for creating libraries of design elements.
1.5 Policies and Policy Classes
Policies and policy classes help in implementing safe, efficient, and highly customizable design elements.
A policy defines a class interface or a class template interface. The interface consists of one or all of the
following: inner type definitions, member functions, and member variables.
Policies have much in common with traits (Alexandrescu 2000a
) but differ in that they put less emphasis
on type and more emphasis on behavior. Also, policies are reminiscent of the Strategy design pattern
(Gamma et al. 1995
), with the twist that policies are compile-time bound.
For example, let's define a policy for creating objects. The Creator policy prescribes a class template of
type
T. This class template must expose a member function called Create that takes no arguments and
returns a pointer to
T. Semantically, each call to Create should return a pointer to a new object of type T.
The exact mode in which the object is created is left to the latitude of the policy implementation.
Let's define some policy classes that implement the Creator policy. One possible way is to use the
new
operator. Another way is to use
malloc and a call to the placement new operator (Meyers 1998b). Yet
another way would be to create new objects by cloning a prototype object. Here are examples of all three
methods:
template <class T>
struct OpNewCreator
{
static T* Create()
{
return new T;
}
};
template <class T>
struct MallocCreator
{
static T* Create()
{
void* buf = std::malloc(sizeof(T));
if (!buf) return 0;
return new(buf) T;
}
};
template <class T>
struct PrototypeCreator
{
PrototypeCreator(T* pObj = 0)
:pPrototype_(pObj)
{}
T* Create()
{
剩余284页未读,继续阅读
![](https://profile-avatar.csdnimg.cn/7ee4b9d740cb4e658452b56bc537b66a_yansandiego.jpg!1)
yansandiego
- 粉丝: 2
上传资源 快速赚钱
我的内容管理 展开
我的资源 快来上传第一个资源
我的收益
登录查看自己的收益我的积分 登录查看自己的积分
我的C币 登录后查看C币余额
我的收藏
我的下载
下载帮助
![](https://csdnimg.cn/release/wenkucmsfe/public/img/voice.245cc511.png)
最新资源
- GuessNumber 2.0版本新增难度选择功能
- 联想一键恢复功能详解及NOVO按键操作指南
- Laravel 8食谱食材:掌握专业级代码轻松制作
- ASP.NET网上人才招聘系统源代码及论文全面解析
- C语言实现环形缓冲区的32位调试库
- qEdit: 基于Qt和C++的开源文本编辑器
- FortiClient 6.0.10.0297 安全软件:Windows系统安装与使用
- GNU Make第三版:深入掌握项目管理与扩展功能
- JUnit4.0版本核心jar包深入解析
- 掌握CSS弹性框与网格布局的秘诀
- 实现全动态的JSON级联select下拉框
- POSIX开源软件:电子商务平台的集成解决方案
- Linux内存管理与虚拟内存管理指南
- ASP科研项目管理系统源码与论文指南
- WPF中使用VideoCaptureElement实现拍照功能教程
- 基于ThinkPHP3.2的微信问卷考试系统源码发布
安全验证
文档复制为VIP权益,开通VIP直接复制
![](https://csdnimg.cn/release/wenkucmsfe/public/img/green-success.6a4acb44.png)