神奇C++问题(7)

HaoGeGe 2001-05-17 02:49:00
请问该段代码的输出结果.

#include <iostream>
#include <functional>
using namespace std;

template<class op>
void g(int size, op o, const char* sep = "") {
for(int i = 0; i < size; ++i)
cout << o(i) << sep;
}

template<class op,int size>
struct w {
char* operator()(int a) {
g(size,binder1st<op>(op(),a),"\t");
return "\n";
}
};

template<class op,int size>
void
x(){
g(size, w<op,size>());
}

int main(){
x<multiplies<int>,6>();
return 0;
}


...全文
209 18 打赏 收藏 转发到动态 举报
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
lvjingchun 2001-05-18
  • 打赏
  • 举报
回复
感谢高手, c++真是奥妙无穷,跟着高手学两招, 没错!
holyfire 2001-05-18
  • 打赏
  • 举报
回复
真的能做到与平台无关吗,我还是有疑问。
catechumen 2001-05-18
  • 打赏
  • 举报
回复
虽然我看了晕了又晕,但我还是非常感谢HaoGeGe(交流的方向)给了一次挑战自我的机会!感谢
你给我们带来的精品食粮!
HaoGeGe 2001-05-18
  • 打赏
  • 举报
回复
http://www.accu.org/c++sig/public/Overload.html
babysloth 2001-05-18
  • 打赏
  • 举报
回复
从哪里找到的,老大?
holyfire 2001-05-18
  • 打赏
  • 举报
回复
好啊,用金山词霸慢慢看先。
HaoGeGe 2001-05-18
  • 打赏
  • 举报
回复
Supporting Threads in Standard C++ Part 1
Phil Bass
Why Threads?
Shortly after the C++ standard was announced I read an article explaining what the standardisation committee will do next. There is still work to do, it said. Although the standard is not allowed to change for 7 years some parts will need to be clarified and ambiguities and inconsistencies will need to be resolved. At some stage the committee will also have to consider the next C++ standard. The article urged its readers to report parts of the existing standard that could be improved and to submit suggestions for its next incarnation.

I thought about this for a while. It seems to me that the biggest problem with C++ is its sheer size and complexity. Commercial organisations need to spend a lot of time and money training C++ programmers. Many of them feel it isn’t worth the investment. What’s wrong with C, Java or Visual Basic? Or simply C++ as a “better C”? To put it another way, C++ is an excellent language for writing efficient, high-quality, low-level software. But most software isn’t like that. So, if C++ is to remain a main-stream language, it needs to become smaller, simpler and easier to use - without losing any of its power and flexibility.

Unfortunately, but I had no idea how this could be done. So I thought some more and decided that the standard could be improved by making it bigger. Like C before it, C++ assumes an underlying (abstract) machine. It is a single-processor, single-process, single-thread machine. And most modern software is written to run in an environment of networked machines running multi-threading, multi-processing operating systems. If standard C++ doesn’t provide support for such environments C++ programs will always be platform-specific and the benefits of having a standard will inevitably be eroded.

A Hierarchy of Architectures
So, I asked myself, “What other facilities do we need in C++ to support modern object-oriented, distributed-processing software environments?”. The answer I gave myself was this:

threads and thread synchronisation mechanisms;
processes, inter-process communications and process synchronisation mechanisms;
networking and support for distributed objects.
1.I thought of this as a hierarchy of architectures, each layer adding facilities to those of the layer beneath. For completeness, I included support for objects in the hierarchy and produced this table.

Architecture
Objects
Threads
Virtual Address Spaces

(processes)
Physical Address Spaces

(processors)

procedural
0
1
1
1

object-oriented
n
1
1
1

multi-threaded
n
n
1
1

multi-processing
n
n
n
1

distributed
n
n
n
n


Figure 1 - A Hierachy of Programming Architectures

Traditional languages, like C, address the procedural level. Object-oriented languages, like C++ and Java, address the object-oriented level.

Multi-threading, multi-processing and distributed architectures are not directly supported by general purpose programming languages(1). That is provided by operating systems and libraries. Perhaps it is time to think about adding these facilities to the C++ Standard Library.

Support for Threads in the Standard Library

Since then I have been prompted to consider multi-threading support for other reasons, too. Recent articles in Overload [1], [2] and the C/C++ Users Journal [3] have described designs that differ from those I’ve seen before. There are also unresolved issues in projects at work related to the use of threads in object-oriented designs.

So, I set myself the task of exploring current ideas and trying to design thread support facilities suitable for inclusion in the standard library. I am aware that this topic has been considered by a number of people more knowledgeable and more able than myself. And I believe that threads were omitted from the standard library partly because existing threading models differ too much for a ‘standard’ solution to be accepted. The aim was not so much to define an extension to the standard, but more to learn about the strengths and weaknesses of different approaches while remaining focused on the essentials.

Survey of Existing Multi-Threading Facilities

I looked at a number of software packages providing threads. They are listed in Table 1.

Category
Package
Description

Operating system kernel API
Win32 Threads
C functions.

Posix Threads
C functions.

Java interface
Java Threads
Java classes.

General-purpose C++ interface
Rogue Wave Threads
C++ classes.

Objectspace Thread Classes
C++ classes.

ACE Thread classes
C++ classes.

John Harrington’s Thread Classes
C++ classes.

Allan Kelly’s Class Templates
C++ class templates.

Bio-Rad’s Thread Classes
C++ classes.

Vendor-specific C++ interface
MFC CThread Class
C++ classes.

OWL TThread Class
C++ classes.

VCL TThread Class
C++/Delphi classes.


Table 1 - A Selection of Thread packages.

The packages fall into three main categories: operating system kernel API, general-purpose C++ interface and vendor-specific interface. In Java the distinction between operating system and language is blurred, so that has been given a category of its own.

Most of the packages listed here have been studied only superficially. The kernel APIs provide the low-level facilities that might be used to implement C++ classes. They were examined to see what building blocks are readily available. Each of the vendor-specific C++ interfaces depends on facilities not directly related to the provision of multi-threading (e.g. Window objects). I regarded these extra facilities as inessential and inappropriate to a standard library. The Java and general-purpose C++ interface packages have much in common and I concentrated on these. It is worth noting, however, that the vendor-specific packages, when stripped of extraneous features, also have much in common with the Java and general-purpose groups.

Core Facilities
I will call the features common to most/all of the general-purpose interfaces (including Java) the core facilities.

Table 2 lists the core facilities together with the corresponding Win32 API functions by way of example. These features define the thread model that will be discussed in this article.

Description
Win32 Function

Create a new thread.
CreateThread

Terminate the thread function.
ExitThread

Get a thread’s exit code.
GetExitCodeThread

Destroy a thread.
CloseHandle

Get a thread’s priority.
GetThreadPriority

Change a thread’s priority.
SetThreadPriority

Suspend execution of the thread function until resumed.
SuspendThread

Resume execution of the thread function.
ResumeThread

Suspend execution until some condition becomes true.
WaitForMultipleObjects, etc.

Suspend execution of the thread function for a specific time period.
Sleep

Get the handle of the currently executing thread.
GetCurrentThread

Get the ID of the currently executing thread.
GetCurrentThreadId

Wait for the thread function to finish.
WaitForSingleObject


Table 2 - Summary of Core Facilities.
In considering these facilities it is important to distinguish between a thread and the function it is executing. A thread is a resource provided by the system; a thread function is a user-defined piece of code.

Other facilities provided by existing implementations include things like: names for threads (useful for diagnostics), access control (does the user have permission to create a thread, change its priority, etc.), ordering relations (operator<(), operator==(), etc.) and support for thread groups. Support for miscellaneous thread facilities and extensions is not discussed here.

The First Design Challenge
The purpose of a thread is to execute a user-defined function asynchronously with respect to other thread functions. And the first design challenge is to provide a way of running thread functions with widely varying interfaces in a thread that accepts only one, simple thread function interface. I call this the function signature mismatch problem.

1.Ideally, we would like the thread library implementation to call an arbitrary, user-defined function, but the C++ language has no construct that lets us call a function with an unknown parameter list. So either we restrict the type of function we can call or we must find an indirect way of calling any given function.

I shall consider three approaches:

Thread-Runs-Function,
Thread-Is-Polymorphic-Object,
Thread-Runs-Polymorphic-Object.
The first two are wide-spread; the third is the one we use at Bio-Rad. The code fragments in Figure 2 through Figure 4 show the characteristic features of each approach.

Thread-Runs-Function
class thread
{
public:
typedef int function (void*);

// create thread and start thread function
thread (function*, void*);
. . .
};
Figure 2 - Thread-Runs-Function
The thread class in the Thread-Runs-Function approach is a thin wrapper around the thread functions in the kernel API. It leaves responsibility for solving the function signature mismatch problem entirely in the hands of the programmer. The user is expected to provide a non-member function (or static member function) with the same signature as the underlying system call. The exact signature of the thread function varies from platform to platform, but typically the thread function takes a void* parameter and returns an integer value (signed or unsigned, int or long).

This is acceptable for procedural-style programming, but less useful for object-oriented designs. Non-member functions are usually inappropriate in O-O designs and adding static member functions just to fit the thread class interface complicates the implementation. And, of course, the use of a void* parameter introduces the risk of unsafe type conversions.

Thread-Is-Polymorphic-Object
class thread
{
public:
// create thread, destroy thread
thread();
virtual ~thread();

// start thread function
void start();
. . .
protected:
// thread function implementation
virtual int run() = 0;
. . .
};
Figure 3 - Thread-Is-Polymorphic-Object
Thread-Is-Polymorphic-Object fits better with the object-oriented style. The static member function is replaced with a virtual member function and the void* parameter is replaced with the implicit this pointer.

The library user must still provide an implementation for a function with a simple, fixed interface - the virtual run() function. However, it is natural to provide any data required by the run() function as data members of the derived class. The absence of a parameter list does not adversely affect the design and the resulting code is elegant and type-safe. The extra level of indirection provided by the virtual function call helps the application developer to solve the function signature mismatch problem.

The need to sub-class thread adds a degree of complexity to the class hierarchies, but this is a small price to pay for the extra convenience and type safety. In a sense, this is what object-oriented programming is all about. However, with this approach the thread management functions are tightly coupled with the application logic and this makes it more difficult to change the application’s threading strategy.

Thread-Runs-Polymorphic-Object
class thread
{
public:
struct function;

// create thread and start thread function
thread (function&);
. . .
};

struct thread::function
{
// destroy thread function
virtual ~function() {}

// thread function implementation
virtual int run() = 0;
};
Figure 4 - Thread-Runs-Polymorphic-Object

The Thread-Runs-Polymorphic-Object approach retains the main characteristics of Thread-Is-Polymorphic-Object, but also separates the concepts of thread and thread function into different classes. The thread class handles thread management, while the thread function class encapsulates the application logic.

Here we have an example of the Command design pattern [4]. The thread class performs the role of the Invoker and the thread function class performs the role of the abstract Command class. It is the responsibility of the application programmer to provide a class derived from thread::function to perform the ConcreteCommand role. As noted in the Gang-of-Four book, a Command is an object-oriented replacement for a call-back function.


Figure 5 - Command Design Pattern
For the Command pattern to work it has to solve the function signature mismatch problem. The abstract Command must provide a simple, fixed execution interface for the Invoker to use. At the same time its implementation must be able to execute an arbitrary action function. It is the ConcreteCommand class that translates the abstract operation into a concrete method call.

The pattern represents a complete solution to the function signature mismatch problem, but the thread library can only provide part of it. The application developer must still provide a concrete class to implement the run() function.

Templates
Designs based on templates are also possible, as described by Allan Kelly in Overloads 31 and 33 ([1], [2]). However, using a class template doesn’t solve the function signature mismatch problem. Even if the thread function type is supplied as a template parameter, the template functions must still assume a fixed interface. In Allan Kelly’s articles, for example, the thread function is assumed to be a class with a Run() member function(2).
Templates may also lead to code bloat. Some of the code in Allan’s thread template is independent of the template parameters and the compiler will duplicate that code for every instantiation of the template. This effect can be controlled by techniques such as factoring out the common behaviour into a non-template base class, using member templates and partial specialisations, but even these techniques are not sufficient here because the duplication occurs within the member functions.
There is a practical problem associated with templates, too. Some compilers still require template function bodies to be placed in the header file and this forces platform-dependent details into the headers. On Win32 systems, for example, it means a #include “Windows.h” in the thread.h file to provide definitions of the HANDLE type, the CreateThread() function, etc.. This leads to #if directives that clutter the headers and long compilation times. Even worse, in my view, it turns what was supposed to be a standard (i.e. non-proprietary) header into one that depends on every platform it supports!
Templates are not all bad, though. Supplying the thread function type as a template parameter removes the need to inherit from a base class and override a virtual function. On the other hand, polymorphism provides its own benefits - late binding allows the dynamic type of the thread function to be determined at run time and this flexibility can be important.
Sample Code
As a concrete example, suppose we want to print a file in the background using the following function:

void Printer::print (std::string file_name);
If our thread library uses the Thread-Runs-Function design we must first create a thread function. We also need a simple struct to pass the printer and file name information to the thread function via its single void* parameter. The print function must decode this structure before calling Printer::print().

// thread function arguments
struct print_args
{
Printer* printer;
std::string* file_name;
};

// thread function
int print_function (void* address)
{
print_args* args = static_cast<print_args*>(address);

Printer* printer = args->printer;
std::string* file_name = args->file_name;

printer->print(*file_name);

return 0;
}
The new print function can be run in a separate thread like this:

Epson_Stylus printer;
std::string file_name("printer_test.txt");

print_args args = {&printer, &file_name};

thread print_thread(print_function,&args);

Using Thread-Runs-Polymorphic-Object we must create a concrete thread function class:

// concrete thread function class
class print_function : public thread::function
{
public:
print_function (Printer& p, std::string n)
: printer(p), file_name(n)
{}
private:
virtual int run()
{
printer.print(file_name);
return 0;
}
Printer& printer;
std::string file_name;
};
Like the print_args struct in the Thread-Runs-Function design the print function class holds information about the printer and the file to be printed. Now, though, the virtual thread function has access to that data via the implicit this pointer. No cast is necessary and no statements are required to decode the data structure.

The new thread class can be used like this:

Epson_Stylus printer;
std::string file_name("printer_test.txt");

print_function print_file(printer,file_name);

thread print_thread(print_function);
The quantity of code is similar in both designs, but the object-oriented style leads to better quality.

Looking Ahead
So far, we have only considered the fundamental mechanism for invoking a user-defined function in a separate thread. Most of the core facilities have not been discussed at all and some of them raise further design issues. There is one problem that is common to many of the core facilities; it is the age old problem of how to balance the need for a simple, consistent interface with the desire to make available the full power of each underlying implementation.

As an example of what I mean, a VGA interface can be provided for just about any video card and this is fine for some applications. But many applications need more advanced graphical operations. Of course there are plenty of video cards to choose from, but they differ in capabilities and native interface. How can we write a portable graphics library that takes advantage of the particular features of each card? Threads present us with several examples of this sort of dilemma and I would like to explore these in the next article.

Then, beyond the basic thread facilities there are issues of ownership and synchronisation. In each of the thread designs presented in this article a parent thread function provides some data to the child. Sometimes responsibility for those data items remains with the parent, sometimes it is passed on to the child. Similarly, a responsible parent will clean up after a child thread dies, but some parents will die themselves, leaving their children orphaned. I suggest these issues are best addressed by a more application-oriented threading model and there is scope for another article here.

Summary
This article has described three common approaches to basic thread facilities and discussed their suitability for inclusion in a standard library. The Thread-Runs-Polymorphic-Object model fits well with object-oriented designs and separates the thread support facilities from the application-specific code.

Providing access to platform-specific facilities and the use of application-oriented thread models were raised as topics for further discussion.

References
[7] Overload, No. 31, April 1999, “Using Templates to Handle Multi-threading” by Allan Kelly.

[8] Overload, No. 33, August 1999, “More Threading with Templates” by Allan Kelly.

[9] C/C++ Users Journal, Vol. 17 No. 8, August 1999, “Win32 Multithreading Made Easy” by John Harrington.

[10] “Design Patterns - Elements of Reusable Object-Oriented Software”, by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides.

[11] The C++ Programming Language, 3rd Edition, by Bjarne Stroustrup.

(1) By “general purpose programming languages” I mean languages that don’t have their own environment. This includes C and C++, but not Smalltalk and Forth. I’m not sure how to classify Java!

(2) More precisely, the template requires the expression ‘object->Run()’ to be well defined where ‘object’ is of type ‘pointer to T’ and ‘T’ is a template parameter.


--------------------------------------------------------------------------------

Copyright © The Association of C & C++ Users 2000. All rights reserved.
holyfire 2001-05-18
  • 打赏
  • 举报
回复
愿闻其详
HaoGeGe 2001-05-18
  • 打赏
  • 举报
回复
这些东西我们编程的时候的确会很少遇到,
但是正是因此我们没有看到C++的全部,
我们变得懒惰,失去了学习的动力,
我觉得学会C++是一个一辈子的事情.
每天都要读上几段代码,看上几篇论文.
例如,最近关于C++将加入对于平台无关多线陈的支持的论文就很有意思吗.
vcbear 2001-05-18
  • 打赏
  • 举报
回复
玩音响有Fans,玩程序也有,这是发烧友级别的程序,妙。好久没有看到这样的好东西了,为什么要上火呢?
holyfire 2001-05-18
  • 打赏
  • 举报
回复
我想HaoGeGe(交流的方向)的目的是让我们好好考虑一下吧,虽然我不赞成这样的编码方式,但对锻炼人的语法和思维有着很好的帮助,也就是所谓的益智游戏罗,大家不要上火啊。
HaoGeGe 2001-05-18
  • 打赏
  • 举报
回复
哈哈,显示我的高明?
我只知道一件事,那就是我一无所知!
把他们贴出来,是希望大家不要做井底之蛙.多关注关注外面的世界!
知道自己的差距!
其实他们都是从
http://www.creport.com/index.cfm
引用的.

This constructor of the x template prints a multiplication table of six rows and six columns, for example:
0 0 0 0 0 0
0 1 2 3 4 5
0 2 4 6 8 10
0 3 6 9 12 15
0 4 8 12 16 20
0 5 10 15 20 25

To understand how this works, we'll first examine the template function g(size,op,sep). op is a function object. The function object is "called" with the first size integers (starting at 0) and the results printed (separated by the string specified by the optional sep argument). We use this function twice.

The first call is in the template function x. It calls g, passing size and an object of type w<op,size>. For our example, this will call g(6,w<multiplies<int>,6>,""), which will cause the following invocations to occur:
w<'multiplies<int>,6>::operator()(0); //rownum = 0
w<multiplies<int>,6>::operator()(1); //rownum = 1
...
w<multiplies<int>,6>::operator()(5); //rownum = 5

Each of these constructors prints a single row of the table. It does this by creating a new function object
binder1st<multiplies<int> >(multiplies(),rownum)

where rownum is the row being printed. This binder1st object is passed to g(), which invokes operator() on the object for each of the integers from 0 to size-1. This prints a single row of the table, using a tab as the separator argument.

binder1st is a standard template class defined in the <functional> header. It takes a binary function object (in this case, multiplies<int>) and a single operand, and creates a new unary function object that "binds" the operand as the first operand of the binary function. This resulting object is invoked by calling operator(), passing a single operand. operator() returns the result of applying the original binary function to the "bound" operand and the operand supplied at the call.

In our example, the row index is "bound" as the first operand. For each column, the invocation of the binder1st object invokes multiplies<int>(int,int), passing the row and column indices respectively; the result is printed by g.

For bonus credit: Explain how the newline at the end of each row is printed.

Parameterizing the operation as a function object allows us to generate tables for other operations. For instance, x<plus<int>,4>() gives us:
0 1 2 3
1 2 3 4
2 3 4 5
3 4 5 6

cber 2001-05-17
  • 打赏
  • 举报
回复
请问你有多少篇Rob Murray的Obfuscated C++?
干脆一次性贴完吧,最好连答案一起给出,省得让人痛苦以显示你的高明:)
HaoGeGe 2001-05-17
  • 打赏
  • 举报
回复
Rob Murray的Obfuscated C++专栏在C++ Report上写了11年.
他本人同时在贝尔实验室工作了14年,参与了世界上第一个C++编译器的开发.
最重要的著作是C++ Strategies and Tactics也在ACCU中得到了Highly Recommended的评价.
如果有时间我以后还将把他的问题贴点上来.
hello_wyq 2001-05-17
  • 打赏
  • 举报
回复
你想要问什么?

孩皮妞野 2001-05-17
  • 打赏
  • 举报
回复
有意义吗? 不要滥用template;
cber 2001-05-17
  • 打赏
  • 举报
回复
打印出来了乘法表,很早以前myan就写过了;-)
holyfire 2001-05-17
  • 打赏
  • 举报
回复
模板好多哦

看看楼下的回答罗

15,440

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 非技术区
社区管理员
  • 非技术区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧