What I Hate About How We Abstract System Dependence

Over the past few years, the style of our abstract interfaces for system-dependent features has bothered me more and more. Here’s my challenge to those happy with the status quo: Find me an interface in Boost or the C++ standard that represents an observable inter-program interface, such that, for any popular operating system, I can’t find a feature of the operating system that is impossible to express using that interface without relying on undefined behavior, undocumented behavior, or circumventing access specifiers. First, a realistic example is in order. We all know and love C++ standard iostreams (“iostreams library,” <http://www.cplusplus.com/ref/iostream>), which is almost certainly the most widespread operating system abstraction in the C++ world. So we create a std::ifstream, and use it to open some file. Now we want to map this file to a region of memory. Do what?! Suddenly, everyone breaks out in laughter, with tears rolling down their cheeks, little globules of spittle escaping from the corners of their mouths, ridiculing the very notion of any standard library being involved in such a reasonable and common operation. But why? The operating system can do this, and it’s not incompatible in the slightest with the concept or interface of std::ifstream. In fact, if we had some way of stealing the secret file descriptor stashed away inside the filebuf, we could do it—but programmers, deep down, feel that this sort of access just isn’t right. Grab some popcorn and watch programmers sweat and squirm in their own cognitive dissonance as they try to find an elegant solution to this insolvable problem (“Legalize access to file descriptors now!” <http://gcc.gnu.org/ml/libstdc++/2005-02/msg00090.html>). Let me get straight to the heart of the problem: we love our concrete interfaces. Every programmer loves the ability to be able to manipulate a class, knowing its complete interface, exactly what sort of animal it is, and exactly what its going to do—and know all of this before the program ever runs for the first time. I hope I’m not bursting anyone’s bubble here, but if when we’re talking about components that abstract system interfaces, then this just isn’t the way the world works. It’s time we grow up, get out of the playpen, and realize that there really is a big scary world of polymorphism out there. My core belief is this. We don’t fully know the capabilities of the system components we manipulate, not at compile time, not at load time, and perhaps not even by the time the program halts. Any interface that pretends to is a lie, and most of the time, a pretty bad liar at that. Here’s another one of my favorite examples, in Boost.Threads (“Boost.Threads,” <http://www.boost.org/doc/html/threads.html>). boost::thread lacks a method to forcefully terminate a thread, despite the fact that many threading systems have one. However, we can’t add one, because there exists at least one threading system that doesn’t have this feature. Well, we *could*, but then we’d be playing a game of chicken with the user daring them to call a function that has completely undecidable behavior. Now let’s say the user was really determined get this feature, and so she decided to write her own thread class. Nope, she loses again! Because her class is not named boost::thread, the class is incompatible with all of the rest of the thread manipulation functions, and so is entirely unusable with Boost.Threads. Do you like scary movies? I have an idea for one. It’s about a future C++ standard that includes a threading library that I can’t use if I want forced termination semantics, or any other feature that any operating system has that the library lacks. Besides the inability of our concrete interfaces to support a capability set that varies, and the lack of ability to reimplement these interfaces compatibly, concrete schemes also can’t represent what I call /multi-interface systems/. Some environments, such as Cygwin (“Cygwin Information and Installation,” <http://www.cygwin.com>), have two (or more) entirely distinct sets of system interfaces that may be used as underlying primitives for a particular concept. In the case of Cygwin and threads, the environment supports both the POSIX and Windows thread interfaces. Both may be used simultaneously, and as each system has its own unique characteristics, it may be entirely reasonable to do so. But, Boost.Threads can’t do this, and really couldn’t possibly be expected to, in its current form. Note that the total set of capabilities is not decidable at compile time, or even load time. For instance, let’s say we’re using a process class on a System V-style system. We’re implementing a debugger, and so we’d like to get access to the process’ core memory through the /proc interface. However, up until we actually try to do so, we really have no way of knowing whether this is supported, as the /proc filesystem may well not be mounted. I’m calling for polymorphism. These interfaces really are conceptually polymorphic; let’s reflect that in our language. Let’s give the user the tools she needs to be able to write her own compatible classes when the ones we write prove insufficient. For the sake of exposition, let me propose a sketch of a possible design for a process class. A generic process is represented by an abstract base class. Derived from it are classes for the major types of processes: POSIX, Windows, DCE, and whatever else. Derived from each of those are specific variants of these types, with additional or extended capabilities. For example, a child of the POSIX class might be a class implementing the ptrace() process debugging interface. Naturally, we’d instantiate objects of these classes with some factory. Since we might not know at creation time exactly what capabilities the system has, or what capabilities are needed, we need a copying mechanism to construct a new process from an old one. This copy might, for example, copy the POSIX base, so as to get the process identifier, and slice the rest off, as unneeded. Clearly there would need to be significant design effort put into this area. This and other implementation issues are mostly tangential from my primary concerns. Finally, pointers are a pain, so we can wrap pointers to a process in your favorite smart pointer. This smart pointer might have additional associated mechanics to enable it to automatically select the best kind of process creation machinery as it is able. I could see this basic process class (which is practically concrete) as having a very pleasant and desirable syntax. Lastly, performance needs to be addressed. I’m not at all worried about making operations that would be normal function calls into virtual function calls, and you shouldn’t be, either. An indirect call will often have a cost an order of magnitude less than the cost of the actual underlying system operation, which might involve many more indirect calls, context switches, and synchronization. A more significant concern is the additional machinery needed to support virtual inheritance. RTTI may also be necessary to fully exercise the class hierarchy’s capabilities. However, when using the subset of features that would be available to an equivalent concrete implementation, RTTI and similar should not be needed, so a user shouldn’t have to pay (too much) for what she’s not using. So here’s my question to the Boost community. How many people have similar concerns and experiences? How often, in real code, do concrete classes prove insufficient? Who here has to entirely reimplement libraries like Boost.Threads for relatively silly reasons? Are there any alternate solutions for the problem I describe? What unforeseen problems might there be with this polymorphic style? Would you use a library such as Boost.Thread if it had been rewritten in this manner? Submitted with Love, Aaron W. LaFramboise

Aaron W. LaFramboise wrote:
Over the past few years, the style of our abstract interfaces for system-dependent features has bothered me more and more. [cut]
Since thread library was the first boost library that I tried to use, right after I "discovered" boost++, I can not agree with you more. My take on it is that people simply don't use it. IMO, current thread library is an old, in those times politically correct, hommage to Java thread classes but in C++. Some past discussion regarding IMO useless thread-specific ptr indicated to me that people involved with threads are either doing something with threads that is way past my capabilities to understand, or that they are simply playing 'syntactic games' since they don't really use threads except maybe to construe material for magazine aricles and useless trivial examples. Tony

Tony Juricic wrote:
Aaron W. LaFramboise wrote:
Over the past few years, the style of our abstract interfaces for system-dependent features has bothered me more and more.
[cut]
Since thread library was the first boost library that I tried to use, right after I "discovered" boost++, I can not agree with you more. My take on it is that people simply don't use it. IMO, current thread library is an old, in those times politically correct, hommage to Java thread classes but in C++.
Some past discussion regarding IMO useless thread-specific ptr indicated to me that people involved with threads are either doing something with threads that is way past my capabilities to understand, or that they are simply playing 'syntactic games' since they don't really use threads except maybe to construe material for magazine aricles and useless trivial examples.
Tone it down. If you think the design of a particular library could be improved, then provide some constructive criticism. If you're only here to rant, take it elsewhere. Doug Gregor Moderator

Douglas Gregor wrote:
If you think the design of a particular library could be improved, then provide some constructive criticism. If you're only here to rant, take it elsewhere.
Sorry, the matter was discussed few months ago ad nauseam and I hardly see what constructive is there to add until there is a plan on rewriting this library. As for the constructive criticism of the library, I have signals in mind. Can you actually give any arguments (and concrete code examples) for why would anybody want to use signals library and what does it really do better than for ex. Java event handlers or C# delegates? Can you imagine and present a simple real-life application and prove all the claims in the chapter about comparisons with other Signal/Slot implemntations? For example, there was some talk here about using signals in boost GUI library. I tried to use it for a simplistic focus management and stumbled upon no-nos, which made me question the utility of the library as it stands now. Tony

Can you imagine and present a simple real-life application and prove all the claims in the chapter about comparisons with other Signal/Slot implemntations? For example, there was some talk here about using signals in boost GUI library. I tried to use it for a simplistic focus management and stumbled upon no-nos, which made me question the utility of the library as it stands now.
I certainly agree with you here. After reading the docs for boost::signal, I'm not sure what are its advantages over std::vector< boost::function<...> > Best, John -- John Torjo, Contributing editor, C/C++ Users Journal -- "Win32 GUI Generics" -- generics & GUI do mix, after all -- http://www.torjo.com/win32gui/ -v1.6.3 (Resource Splitter) -- http://www.torjo.com/cb/ - Click, Build, Run!

Tony Juricic wrote:
As for the constructive criticism of the library, I have signals in mind. Can you actually give any arguments (and concrete code examples) for why would anybody want to use signals library and what does it really do better than for ex. Java event handlers or C# delegates?
Don't know about the latter, but a real case where signals are perfect is to inform 'interested parties' when a child process has exited. Other asynchronous events also fit the bill perfectly here. John Torjo wrote:
Can you imagine and present a simple real-life application and prove all the claims in the chapter about comparisons with other Signal/Slot implemntations? For example, there was some talk here about using signals in boost GUI library. I tried to use it for a simplistic focus management and stumbled upon no-nos, which made me question the utility of the library as it stands now.
I certainly agree with you here. After reading the docs for boost::signal, I'm not sure what are its advantages over std::vector< boost::function<...> >
The primary advantage as I see it is that it won't dispatch to a class' member function when the "this" pointer has been destroyed. (So long as the class derives from boost::trackable.) Ie, Boost.Signals is safe where vector<boost::function<...> > is not. SigC++ has similar functionality. One signal library ( SigCX, http://libsigcx.sourceforge.net ) which derives from SigC++ will allow the user to tunnel signals between threads. Don't know how important this is in the real world, but it would give functionality similar to Windows messages, no? Unfortunates are the licence and the fact that the library is no longer maintained. (Private communication with author.) Regards, Angus

Angus Leeming wrote:
Don't know about the latter, but a real case where signals are perfect is to inform 'interested parties' when a child process has exited. Other asynchronous events also fit the bill perfectly here.
Why on earth would you use signals for that? What's wrong with a simple list where you store interested parties and then invoke the method on the object containing the list, method that would walk the list and inform all the interested parties? You are trading at most 50 lines of simple, debuggable code with hundreds of lines of unfathomable, layers of layers upon layers of templates of uber-complicated code that just manages to invoke a method on a bunch of objects? Really, my questions from the past post are in part rhetorical - that is, I would like to use signals (hopefully some future, enhanced variation) for GUI event notifications (across threads as well). Tony

[snip]
Let me get straight to the heart of the problem: we love our concrete interfaces. Every programmer loves the ability to be able to manipulate a class, knowing its complete interface, exactly what sort of animal it is, and exactly what its going to do—and know all of this before the program ever runs for the first time.
Thats because it makes design and testing easier (ie more predictable), as we are capable of envisioning the implementation...
My core belief is this. We don’t fully know the capabilities of the system components we manipulate, not at compile time, not at load time, and perhaps not even by the time the program halts. Any interface that pretends to is a lie, and most of the time, a pretty bad liar at that.
I'd argue that we know exactly - no more, no less -> either the operating system supports a capability, or it doesn't. The only real question is, 'when do we detect the capabilities, at build time? at run time?'
Here’s another one of my favorite examples, in Boost.Threads (“Boost.Threads,” <http://www.boost.org/doc/html/threads.html>). boost::thread lacks a method to forcefully terminate a thread, despite the fact that many threading systems have one.
Thats because most forcible terminations result in undefined behaviour. Think: "If I have locked a mutex in a worker thread, then I forcibly kill that thread, what state is the mutex in?" So most people go with an implementation that does not provide forcible termination.
However, we can’t add one, because there exists at least one threading system that doesn’t have this feature. Well, we *could*, but then we’d be playing a game of chicken with the user daring them to call a function that has completely undecidable behavior. Now let’s say the user was really determined get this feature, and so she decided to write her own thread class. Nope, she loses again! Because her class is not named boost::thread, the class is incompatible with all of the rest of the thread manipulation functions, and so is entirely unusable with Boost.Threads.
Boost provides that user with predictable results - forcible termination often results in non-predictable behaviour.
Do you like scary movies? I have an idea for one. It’s about a future C++ standard that includes a threading library that I can’t use if I want forced termination semantics, or any other feature that any operating system has that the library lacks.
This already happens in the present -> compare one *nix to another, even to various flavours of win32...
Besides the inability of our concrete interfaces to support a capability set that varies,
The Boost concrete interfaces only provide non-varying implementations so that the code is predictable on all supported platforms. Would you prefer that you would need to test your code on every revision of every platform?
and the lack of ability to reimplement these interfaces compatibly, concrete schemes also can’t represent what I call /multi-interface systems/. Some environments, such as Cygwin (“Cygwin Information and Installation,” <http://www.cygwin.com>), have two (or more) entirely distinct sets of system interfaces that may be used as underlying primitives for a particular concept. In the case of Cygwin and threads, the environment supports both the POSIX and Windows thread interfaces. Both may be used simultaneously, and as each system has its own unique characteristics, it may be entirely reasonable to do so. But, Boost.Threads can’t do this, and really couldn’t possibly be expected to, in its current form.
Its current form is a result of understanding the fundamental problems: - single reasonable useful API - an API that works across most platforms - an implementation that is predictable across the supported platforms
Note that the total set of capabilities is not decidable at compile time, or even load time. For instance, let’s say we’re using a process class on a System V-style system. We’re implementing a debugger, and so we’d like to get access to the process’ core memory through the /proc interface. However, up until we actually try to do so, we really have no way of knowing whether this is supported, as the /proc filesystem may well not be mounted.
The proc filesystem is a directory structure - not a POSIX definition - the comparison is invalid. In the example of a debugger, what does the debuggin process do if there is no debugging capabilities for that platform? Again the problem is "at what point do we detect the available capabilities".
I’m calling for polymorphism. These interfaces really are conceptually polymorphic; let’s reflect that in our language. Let’s give the user the tools she needs to be able to write her own compatible classes when the ones we write prove insufficient.
I'd argue that the capabilities arn't polymorphic. ie you simply couldn't do somthing like this: class Process { virtual void run(); } class HPUX_with_slash_proc : public Process { std::string getProcPath(); } class HPUX_without_slash_proc : public Process { } //Now lets try to use them: Process p = ProcessFactory::getProcess("some_task"); p->getProcPath(); Now what is meant to happen?
For the sake of exposition, let me propose a sketch of a possible design for a process class. A generic process is represented by an abstract base class. Derived from it are classes for the major types of processes: POSIX, Windows, DCE, and whatever else. Derived from each of those are specific variants of these types, with additional or extended capabilities. For example, a child of the POSIX class might be a class implementing the ptrace() process debugging interface.
Naturally, we’d instantiate objects of these classes with some factory.
So that you essentially get objects which will have the same 'generic / portable' API, but you also get the OS specific functionality. Then the programmer decides to use that OS specific functionality. The end result? Non-portable code. Why doesn't the programmer simply just the use the native API...? Why bother using Boost at all?
Since we might not know at creation time exactly what capabilities the system has, or what capabilities are needed, we need a copying mechanism to construct a new process from an old one. This copy might, for example, copy the POSIX base, so as to get the process identifier, and slice the rest off, as unneeded. Clearly there would need to be significant design effort put into this area. This and other implementation issues are mostly tangential from my primary concerns.
The reality of programming for multiple environments is that "implementation issues" are usually the prime reason why, as your threading example highlights, API's tend to be for the lowest common denominator...
Lastly, performance needs to be addressed. I’m not at all worried about making operations that would be normal function calls into virtual function calls, and you shouldn’t be, either. An indirect call will often have a cost an order of magnitude less than the cost of the actual underlying system operation, which might involve many more indirect calls, context switches, and synchronization. A more significant concern is the additional machinery needed to support virtual inheritance.
I dont understand... doesn't: "A more significant concern is the additional machinery needed to support virtual inheritance." conflict with: "I’m not at all worried about making operations that would be normal function calls into virtual function calls..."
RTTI may also be necessary to fully exercise the class hierarchy’s capabilities. However, when using the subset of features that would be available to an equivalent concrete implementation, RTTI and similar should not be needed, so a user shouldn’t have to pay (too much) for what she’s not using.
So here’s my question to the Boost community.
How many people have similar concerns and experiences?
Probably everybody...
How often, in real code, do concrete classes prove insufficient?
All the time...
Who here has to entirely reimplement libraries like Boost.Threads for relatively silly reasons?
The Boost.Threads problem you have mentioned above is not silly - if you have a solution for point that I highlighted, please let us know as we would be extremely interested in the implementation.
Are there any alternate solutions for the problem I describe? What unforeseen problems might there be with this polymorphic style?
See above. Also, what you are describing appears the be a style of that used in the late 80's and early 90's... nice and deep class hierachies (which we now know through experience that it was not such a good fit for a lot of designs...).
Would you use a library such as Boost.Thread if it had been rewritten in this manner?
No - it is not deterministic. I apologise if my response appears head-string - its not meant to be... If you have specific solutions please explain them. regards Mathew

Mathew Robertson wrote:
I'd argue that we know exactly - no more, no less -> either the operating system supports a capability, or it doesn't. The only real question is, 'when do we detect the capabilities, at build time? at run time?'
I think we need more granularity than this. One important counterexample is the case when a capability may be enabled while the operating system is running. For example, a driver may be loaded that presents a high-quality random number source.
Here’s another one of my favorite examples, in Boost.Threads (“Boost.Threads,” <http://www.boost.org/doc/html/threads.html>). boost::thread lacks a method to forcefully terminate a thread, despite the fact that many threading systems have one.
So most people go with an implementation that does not provide forcible termination.
OK. I'm not concerned with this particular feature, but the fact that there will always be some feature that the system supports that a user may want that Boost.Threads will not implement. If its not this, then it might be thread priority, and if not that, then it might be setting a thread's processor affinity, or who knows what else. My point is that the concrete Boost.Thread is not open for extension. If you want a feature it doesn't provide, you either have to modify the sources, or just write yourself an entirely new thread ibrary. This is should not be the way we do it in C++.
Besides the inability of our concrete interfaces to support a capability set that varies,
The Boost concrete interfaces only provide non-varying implementations so that the code is predictable on all supported platforms. Would you prefer that you would need to test your code on every revision of every platform?
With the polymorphic scheme I propose, if you only want the least common denominator interface, then thats all you have to use, and there is no changes needed to your testing procedure. Furthermore, static, compile-time checking can still be achieved by using concrete factories. For example, if you'd like compilation to fail if POSIX facilities are unavailible, code like this could be written. Process p = POSIXProcessFactory::getProcess("some_task"); p->getProcPath();
Its current form is a result of understanding the fundamental problems: - single reasonable useful API - an API that works across most platforms - an implementation that is predictable across the supported platforms
I am not proposing altering these guarantees--and for what it's worth, I agree these are very important characteristics of Boost libraries. However, I am proposing that in addition to providing this basic, portable, guaranteed interface, we allow the user to 'upcast' to system-specific interfaces, with compile-time or runtime checking, at their option.
The proc filesystem is a directory structure - not a POSIX definition - the comparison is invalid.
In the example of a debugger, what does the debuggin process do if there is no debugging capabilities for that platform? Again the problem is "at what point do we detect the available capabilities".
Presumably the factory/copy operation fails, and the debugger has to cope with this situation. This is unavoidable in any case, regardless of what abstraction is being used, as it is impossible to determine if /proc is mounted at compile-time.
I’m calling for polymorphism. These interfaces really are conceptually polymorphic; let’s reflect that in our language. Let’s give the user the tools she needs to be able to write her own compatible classes when the ones we write prove insufficient.
I'd argue that the capabilities arn't polymorphic. ie you simply couldn't do somthing like this:
class Process { virtual void run(); }
Let me re-arrange your example slightly. class Process_with_slash_proc : virtual public Process { // ... }; Process p = ProcessFactory::getProcess("some_task"); try { Process_with_slash_proc p2 = Clone_process_with_slash_proc(p); p2->getProcPath(); } catch(something &) { // ... } This sort of runtime detection would presumably by a rare case, but its an important case! This system also allows us to do things like load a DLL at runtime that knows how to perform some sort of operation on a Process that the user, or the original author of Process, doesn't even know about. A more common case: class signalable_process : virtual public Process { public: int send_signal(int); }; class POSIX_process : public signalable_process /* , ... */ { // ... }; POSIX_Process p = POSIXProcessFactory::getProcess("some_task"); p.send_signal(42); Again, this is just an naïve example. While this isn't identical to a real design, it shows how we can reflect capability sets through inheritance. One could imagine having an HPUX class that derived from the POSIX class that presented additional HPUX-specific interfaces.
So that you essentially get objects which will have the same generic / portable' API, but you also get the OS specific functionality.
Exactly! This is exactly what I'm getting at.
Then the programmer decides to use that OS specific functionality. The end result? Non-portable code.
The programmer deliberately chose to use this non-portable functionality, and presumably he knew exactly what he was doing. C++'s type safety leaves very little room for accidents.
Why doesn't the programmer simply just the use the native API...? Why bother using Boost at all?
So he can reuse the Boost code. For example, the process class already does 95% of what he needs. He just needs one other feature it's missing. To me, this is the essence of re-use, and the open-closed principle, and what C++ is supposed to be. It's very odd to me that you think it would be better to refuse to allow the programmer to trivially extend the class, and instead force him to reimplement the entire process library. Besides that, the ability to extend in this manner leads to a common framework. Unlike the bad kind of framework that tends to lock people out more than anythin else, this kind of framework provides common ground on which people may make separate, but compatible, extensions. For instance, Katie might implement a class that sends signals to a process, and Amy might implement a class that waits for process termination, and Nicole could use both of these capabilities together, on the same process, at the same time. This is reuse.
I dont understand... doesn't:
"A more significant concern is the additional machinery needed to support virtual inheritance."
conflict with:
"I’m not at all worried about making operations that would be normal function calls into virtual function calls..."
In the former quote, I'm talking about the cost associated with doing upcasts with dynamic_cast, and similar operations. In the latter, I'm talking about the simple indirect calls. For someone who only chooses to use the least common denominator functionality, they will only need to be concerned with the latter sort overhead.
Would you use a library such as Boost.Thread if it had been rewritten in this manner?
No - it is not deterministic.
I don't understand your objection. If you choose to use only the least common denominator functionality, I'd expect the behavior to be, formally and practically, identical to the status quo concrete class. However, for those users who need features only availible in some environments, they are free to re-use the existing code, plus add their own code to support their particular use-case. I can't beleive that it is better to *not* give a user a chance at extending the class. Thank you for the detailed analysis and criticism, Aaron W. LaFramboise

"Aaron W. LaFramboise" <aaronrabiddog51@aaronwl.com> writes:
Mathew Robertson wrote:
I'd argue that we know exactly - no more, no less -> either the operating system supports a capability, or it doesn't. The only real question is, 'when do we detect the capabilities, at build time? at run time?'
I think we need more granularity than this. One important counterexample is the case when a capability may be enabled while the operating system is running. For example, a driver may be loaded that presents a high-quality random number source.
Here’s another one of my favorite examples, in Boost.Threads (“Boost.Threads,” <http://www.boost.org/doc/html/threads.html>). boost::thread lacks a method to forcefully terminate a thread, despite the fact that many threading systems have one.
So most people go with an implementation that does not provide forcible termination.
OK. I'm not concerned with this particular feature, but the fact that there will always be some feature that the system supports that a user may want that Boost.Threads will not implement. If its not this, then it might be thread priority, and if not that, then it might be setting a thread's processor affinity, or who knows what else.
My point is that the concrete Boost.Thread is not open for extension. If you want a feature it doesn't provide, you either have to modify the sources, or just write yourself an entirely new thread ibrary. This is should not be the way we do it in C++.
I have always thought that concrete implementations of portable libraries should supply users a way to get at the OS-specific data so they can use platform-specific facilities on them if neccessary. You have to understand, the Boost.Threads specification was designed to become a standard, and as such it didn't include anything nonportable. That said, there's no reason we shouldn't expose the low-level thread IDs and document them in a special section on platform specifics. -- Dave Abrahams Boost Consulting www.boost-consulting.com

From: David Abrahams <dave@boost-consulting.com>
I have always thought that concrete implementations of portable libraries should supply users a way to get at the OS-specific data so they can use platform-specific facilities on them if neccessary.
Exactly what I was thinking. That's all it takes, but you don't want to make it syntactically quiet to get at the underlying OS-specific data. IOW, you want something akin to the new-style casts in verbosity so the switch to non-portable code is loud and clear. Doing that via a derived class is one way. Using a cast to the OS-specific type within the portable facade is another. (The latter doesn't introduce polymorphic side effects such as call overhead or slicing.) boost::whatever w; special_type s(boost::platform_cast<special_type>(w));
You have to understand, the Boost.Threads specification was designed to become a standard, and as such it didn't include anything nonportable. That said, there's no reason we shouldn't expose the low-level thread IDs and document them in a special section on platform specifics.
If the platform specifics are kept from the portable interface -- through implementation magic -- then we get both properties. Documentation must also mention what not to do via that non-portable mechanism. IOW, there are many things one can do via low level APIs that can break invariants, so restrictions must be placed upon what can be done or more interface is needed to allow the portable interface to adapt to the effects of the non-portable code path. -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

Rob Stewart <stewart@sig.com> writes:
From: David Abrahams <dave@boost-consulting.com>
I have always thought that concrete implementations of portable libraries should supply users a way to get at the OS-specific data so they can use platform-specific facilities on them if neccessary.
Exactly what I was thinking. That's all it takes, but you don't want to make it syntactically quiet to get at the underlying OS-specific data. IOW, you want something akin to the new-style casts in verbosity so the switch to non-portable code is loud and clear.
Doing that via a derived class is one way. Using a cast to the OS-specific type within the portable facade is another. (The latter doesn't introduce polymorphic side effects such as call overhead or slicing.)
boost::whatever w; special_type s(boost::platform_cast<special_type>(w));
You have to understand, the Boost.Threads specification was designed to become a standard, and as such it didn't include anything nonportable. That said, there's no reason we shouldn't expose the low-level thread IDs and document them in a special section on platform specifics.
If the platform specifics are kept from the portable interface -- through implementation magic -- then we get both properties.
I think that's much too fancy. Put a free function to access the platform-specific data in a separate header and you're done.
Documentation must also mention what not to do via that non-portable mechanism. IOW, there are many things one can do via low level APIs that can break invariants, so restrictions must be placed upon what can be done or more interface is needed to allow the portable interface to adapt to the effects of the non-portable code path.
Of course. -- Dave Abrahams Boost Consulting www.boost-consulting.com

From: David Abrahams <dave@boost-consulting.com>
Rob Stewart <stewart@sig.com> writes:
From: David Abrahams <dave@boost-consulting.com>
I have always thought that concrete implementations of portable libraries should supply users a way to get at the OS-specific data so they can use platform-specific facilities on them if neccessary.
Exactly what I was thinking. That's all it takes, but you don't want to make it syntactically quiet to get at the underlying OS-specific data. IOW, you want something akin to the new-style casts in verbosity so the switch to non-portable code is loud and clear.
Doing that via a derived class is one way. Using a cast to the OS-specific type within the portable facade is another. (The latter doesn't introduce polymorphic side effects such as call overhead or slicing.)
boost::whatever w; special_type s(boost::platform_cast<special_type>(w));
You have to understand, the Boost.Threads specification was designed to become a standard, and as such it didn't include anything nonportable. That said, there's no reason we shouldn't expose the low-level thread IDs and document them in a special section on platform specifics.
If the platform specifics are kept from the portable interface -- through implementation magic -- then we get both properties.
I think that's much too fancy. Put a free function to access the platform-specific data in a separate header and you're done.
special_type == platform-specific data platform_cast == free function What's "much too fancy" in my suggestion? I didn't mention a separate header, but there's little difference otherwise. -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

Rob Stewart <stewart@sig.com> writes:
From: David Abrahams <dave@boost-consulting.com>
Rob Stewart <stewart@sig.com> writes:
From: David Abrahams <dave@boost-consulting.com>
I have always thought that concrete implementations of portable libraries should supply users a way to get at the OS-specific data so they can use platform-specific facilities on them if neccessary.
Exactly what I was thinking. That's all it takes, but you don't want to make it syntactically quiet to get at the underlying OS-specific data. IOW, you want something akin to the new-style casts in verbosity so the switch to non-portable code is loud and clear.
Doing that via a derived class is one way. Using a cast to the OS-specific type within the portable facade is another. (The latter doesn't introduce polymorphic side effects such as call overhead or slicing.)
boost::whatever w; special_type s(boost::platform_cast<special_type>(w));
You have to understand, the Boost.Threads specification was designed to become a standard, and as such it didn't include anything nonportable. That said, there's no reason we shouldn't expose the low-level thread IDs and document them in a special section on platform specifics.
If the platform specifics are kept from the portable interface -- through implementation magic -- then we get both properties.
I think that's much too fancy. Put a free function to access the platform-specific data in a separate header and you're done.
special_type == platform-specific data platform_cast == free function
What's "much too fancy" in my suggestion? I didn't mention a separate header, but there's little difference otherwise.
You mentioned "implementation magic." I thought you had something elaborate in mind. -- Dave Abrahams Boost Consulting www.boost-consulting.com

Rob Stewart <stewart <at> sig.com> writes:
From: David Abrahams <dave <at> boost-consulting.com>
You have to understand, the Boost.Threads specification was designed to become a standard, and as such it didn't include anything nonportable. That said, there's no reason we shouldn't expose the low-level thread IDs and document them in a special section on platform specifics.
If the platform specifics are kept from the portable interface -- through implementation magic -- then we get both properties.
Documentation must also mention what not to do via that non-portable mechanism. IOW, there are many things one can do via low level APIs that can break invariants, so restrictions must be placed upon what can be done or more interface is needed to allow the portable interface to adapt to the effects of the non-portable code path.
Another possibility is to provide another level of indirection. For example, boost::thread can have an accessor which returns a boost::platform_thread, which has the OS thread ID, plus member functions which mimic OS operations not reflected in the portable (boost::thread) API. The advantage of this approach is that boost::platform_thread member functions may be able to preserve invariants that otherwise might be broken if the user was allowed to manipulate the underlying OS thread ID itself, and if the coverage of the boost::platform_thread member functions is large enough, it might not even be necessary to expose the OS thread ID at all. Just a thought. Bob

From: Bob Bell <belvis@pacbell.net>
Another possibility is to provide another level of indirection. For example, boost::thread can have an accessor which returns a boost::platform_thread, which has the OS thread ID, plus member functions which mimic OS operations not reflected in the portable (boost::thread) API. The advantage of this approach is that boost::platform_thread member functions may be able to preserve invariants that otherwise might be broken if the user was allowed to manipulate the underlying OS thread ID itself, and if the coverage of the boost::platform_thread member functions is large enough, it might not even be necessary to expose the OS thread ID at all.
That means that everything possible on a given OS should be provided -- ultimately, anyway -- but at least it would be done once, right, and safely. I like it. The concern is how that type is related to the portable type. If it is to maintain invariants, it has to know a great deal about the portable type. Perhaps it could contain a (reference to a) special type derived from the portable type that provides access to more implementation details/APIs that enable it to maintain invariants while it offers an expanded interface. -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

<snip>
Here's another one of my favorite examples, in Boost.Threads ("Boost.Threads," <http://www.boost.org/doc/html/threads.html>). boost::thread lacks a method to forcefully terminate a thread, despite the fact that many threading systems have one.
So most people go with an implementation that does not provide forcible termination.
But not all. For example, we've got a large distributed system, many servers, many threads. During shutdown, we find it better behaviour to have a server kill an offending thread, complete the shutdown, and then report the problem somewhere as compared to the other behaviour of simply having the server hang until the operator has to intervene and kill it . Hence, I see a lot of value in even this particular example. <snip>
OK. I'm not concerned with this particular feature, but the fact that there will always be some feature that the system supports that a user may want that Boost.Threads will not implement. If its not this, then it might be thread priority, and if not that, then it might be setting a thread's processor affinity, or who knows what else.
My point is that the concrete Boost.Thread is not open for extension. If you want a feature it doesn't provide, you either have to modify the sources, or just write yourself an entirely new thread ibrary. This is should not be the way we do it in C++.
I have always thought that concrete implementations of portable libraries should supply users a way to get at the OS-specific data so they can use platform-specific facilities on them if neccessary.
You have to understand, the Boost.Threads specification was designed to become a standard, and as such it didn't include anything nonportable. That said, there's no reason we shouldn't expose the low-level thread IDs and document them in a special section on platform specifics.
========================================================== And then, if I wanted to write a "kill thread" method, I'd write a function that took the boost::thread, got the thread id, and called the appropriate system function? I could live with that - that way'd I'd at least be able to use the rest of the boost::thread libary in my application. One downside would that pretty soon there would be many many bootleg versions of the "kill thread" method and other platform-specific stuff for the platforms. Some of these versions would implement "kill thread" etc correctly, some would have subtle bugs, there would be different function prototypes, etc. I'd much prefer that if someelse has implemented the O/S specific features for threads on my platform, that way I'd benefit from the bug fixes and I'd be able to reuse the code. If the standard isn't a place where that type of code can be collected, where is the right place? Is there room for defining a mechanism for adding platform-specific extensions that contain concepts that may or may not be supported on all versions of the library? Thanks, Rob.

Here's another one of my favorite examples, in Boost.Threads ("Boost.Threads," <http://www.boost.org/doc/html/threads.html>). boost::thread lacks a method to forcefully terminate a thread, despite the fact that many threading systems have one.
So most people go with an implementation that does not provide forcible termination.
But not all. For example, we've got a large distributed system, many servers, many threads. During shutdown, we find it better behaviour to have a server kill an offending thread, complete the shutdown, and then report the problem somewhere as compared to the other behaviour of simply having the server hang until the operator has to intervene and kill it .
Hence, I see a lot of value in even this particular example.
Forcibly kill threads is never going to be a safe thing to do in C++, as it doesn't (yet) provide the ability to automatically unlock resources (unless it is using RAII). That said, I also have had a need to forcibly kill a thread - I agree that it is useful functionality - I just said that it was hard to do safely... <snip>
And then, if I wanted to write a "kill thread" method, I'd write a function that took the boost::thread, got the thread id, and called the appropriate system function?
I could live with that - that way'd I'd at least be able to use the rest of the boost::thread libary in my application.
One downside would that pretty soon there would be many many bootleg versions of the "kill thread" method and other platform-specific stuff for the platforms. Some of these versions would implement "kill thread" etc correctly, some would have subtle bugs, there would be different function prototypes, etc. I'd much prefer that if someelse has implemented the O/S specific features for threads on my platform, that way I'd benefit from the bug fixes and I'd be able to reuse the code.
If the standard isn't a place where that type of code can be collected, where is the right place? Is there room for defining a mechanism for adding platform-specific extensions that contain concepts that may or may not be supported on all versions of the library?
...and there inlies the real question... isn't a public forum a great thing ! Mathew

David Abrahams wrote:
I have always thought that concrete implementations of portable libraries should supply users a way to get at the OS-specific data so they can use platform-specific facilities on them if neccessary.
I've been thinking about this, but this continues to bother me. Is this essentially different from removing the private access specifier from the class definition? It seems to me that, by doing this, we are throwing away so many correctness guarantees. To me, this seems sort of like taking shared_ptr, and making it implicitly convertable to a pointer. I think that promoting this sort of behavior will cause programs that have a much lower probability of being correct. I'd be a lot more happy about a system that forced code that wanted to access these internals needed for extension to do something special, besides just calling a semi-secret method. For example, derivation does this for me--but there might be other ways, that aren't apparent to me at the moment. Aaron W. LaFramboise

"Aaron W. LaFramboise" <aaronrabiddog51@aaronwl.com> writes:
David Abrahams wrote:
I have always thought that concrete implementations of portable libraries should supply users a way to get at the OS-specific data so they can use platform-specific facilities on them if neccessary.
I've been thinking about this, but this continues to bother me. Is this essentially different from removing the private access specifier from the class definition?
It seems to me that, by doing this, we are throwing away so many correctness guarantees. To me, this seems sort of like taking shared_ptr, and making it implicitly convertable to a pointer. I think that promoting this sort of behavior will cause programs that have a much lower probability of being correct.
I'd be a lot more happy about a system that forced code that wanted to access these internals needed for extension to do something special, besides just calling a semi-secret method. For example, derivation does this for me--but there might be other ways, that aren't apparent to me at the moment.
I can't understand why derivation is any more-special than anything else. Furthermore, IIUC what you're suggesting, it has interoperability problems. What do you do if you write a library that needs to take advantage of some of these platform-specifics, but wants to traffic in the portable type in its public interface? This is a simple problem and not worth establishing elaborate "protection" schemes for. Every C++ user knows there are things you can do portably, and other things that are platform-dependent. You can use the C++ standard library portably (for the most part ;->) but the minute you #include <Windows.h> or <pthreads.h> you're in platform-specific land. I don't see why this should be any different. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
"Aaron W. LaFramboise" <aaronrabiddog51@aaronwl.com> writes:
Mathew Robertson wrote:
I have always thought that concrete implementations of portable libraries should supply users a way to get at the OS-specific data so they can use platform-specific facilities on them if neccessary.
I agree, just like get method of shared_ptr... you can break invariants if you want: delete p->get(); But you were warned to not do so... That way just creates errors who really want and we still got more flexibility than the interface can provide as platform-indepedent.
You have to understand, the Boost.Threads specification was designed to become a standard, and as such it didn't include anything nonportable. That said, there's no reason we shouldn't expose the low-level thread IDs and document them in a special section on platform specifics.
-- Felipe Magno de Almeida UIN: 2113442 email: felipe.almeida at ic unicamp br, felipe.m.almeida at gmail com, felipe at synergy com I am a C, modern C++, MFC, ODBC, Windows Services, MAPI developer from synergy, and Computer Science student from State University of Campinas(UNICAMP). To know more about: Unicamp: http://www.ic.unicamp.br Synergy: http://www.synergy.com.br current work: http://www.mintercept.com

I'd argue that we know exactly - no more, no less -> either the operating system supports a capability, or it doesn't. The only real question is, 'when do we detect the capabilities, at build time? at run time?'
I think we need more granularity than this. One important counterexample is the case when a capability may be enabled while the operating system is running. For example, a driver may be loaded that presents a high-quality random number source.
How can you get more granularity than run time?
Here’s another one of my favorite examples, in Boost.Threads (“Boost.Threads,” <http://www.boost.org/doc/html/threads.html>). boost::thread lacks a method to forcefully terminate a thread, despite the fact that many threading systems have one.
So most people go with an implementation that does not provide forcible termination.
OK. I'm not concerned with this particular feature, but the fact that there will always be some feature that the system supports that a user may want that Boost.Threads will not implement. If its not this, then it might be thread priority, and if not that, then it might be setting a thread's processor affinity, or who knows what else.
Setting the processor affinity on FreeBSD 4.x (non-existant setAfinity function) or Linux 2.4 (returns Esomthing) doesn't work.
My point is that the concrete Boost.Thread is not open for extension. If you want a feature it doesn't provide, you either have to modify the sources, or just write yourself an entirely new thread ibrary. This is should not be the way we do it in C++.
This is exactly the point of C++ -> it provides you a way to extend existing code through polymorphism and/or encapsulation. If those techniques dont work, you can simply a) implement you own code, b) inport third-party code. It appears that Boost.Thread doesn't provide the functionality that you need - which may be a reasonable claim (I personally like the ZThread library). In which case, then I'd suggest you offer up an alternative for the implementation rather than saying something about the magic of polymorphism, as most people on the list are well aware of the concept.
Besides the inability of our concrete interfaces to support a capability set that varies,
The Boost concrete interfaces only provide non-varying implementations so that the code is predictable on all supported platforms. Would you prefer that you would need to test your code on every revision of every platform?
With the polymorphic scheme I propose, if you only want the least common denominator interface, then thats all you have to use, and there is no changes needed to your testing procedure.
Furthermore, static, compile-time checking can still be achieved by using concrete factories. For example, if you'd like compilation to fail if POSIX facilities are unavailible, code like this could be written.
Process p = POSIXProcessFactory::getProcess("some_task"); p->getProcPath();
... and how is this portable to win32 / VMS / etc, without writing #define's everywhere?
Its current form is a result of understanding the fundamental problems: - single reasonable useful API - an API that works across most platforms - an implementation that is predictable across the supported platforms
I am not proposing altering these guarantees--and for what it's worth, I agree these are very important characteristics of Boost libraries.
However, I am proposing that in addition to providing this basic, portable, guaranteed interface, we allow the user to 'upcast' to system-specific interfaces, with compile-time or runtime checking, at their option.
ok - that wasn't clear, and FWIW I tend to agree on that point, ie: the programmer should be able to get access to the native capabilities if they should choose to do so. However I'm not sure that I would want to see POSIXProcessFactory in the API... However, getting access to the OS-specific handle would be useful.
The proc filesystem is a directory structure - not a POSIX definition - the comparison is invalid.
In the example of a debugger, what does the debuggin process do if there is no debugging capabilities for that platform? Again the problem is "at what point do we detect the available capabilities".
Presumably the factory/copy operation fails, and the debugger has to cope with this situation. This is unavoidable in any case, regardless of what abstraction is being used, as it is impossible to determine if /proc is mounted at compile-time.
Of course... but I would expect the Boost API to be along the lines of: Debugger d = DebuggerFactory::getInstance() not: Debugger d = SlashDevDebuggerFactory::getInstance()
I’m calling for polymorphism. These interfaces really are conceptually polymorphic; let’s reflect that in our language. Let’s give the user the tools she needs to be able to write her own compatible classes when the ones we write prove insufficient.
I'd argue that the capabilities arn't polymorphic. ie you simply couldn't do somthing like this:
class Process { virtual void run(); }
Let me re-arrange your example slightly.
class Process_with_slash_proc : virtual public Process { // ... };
Process p = ProcessFactory::getProcess("some_task"); try { Process_with_slash_proc p2 = Clone_process_with_slash_proc(p); p2->getProcPath(); } catch(something &) { // ... }
This sort of runtime detection would presumably by a rare case, but its an important case! This system also allows us to do things like load a DLL at runtime that knows how to perform some sort of operation on a Process that the user, or the original author of Process, doesn't even know about.
I'd like to mention one point:
Process p = ProcessFactory::getProcess("some_task");
contradicts your example of:
Process p = POSIXProcessFactory::getProcess("some_task");
I think I understand what you are trying to say, something along the lines of "lets use factory methods for generating the instances, so that, at run time, we get the most capabilites available from the OS" - which I throughly agree with - I'm just not sure that you have considered some of the examples.
A more common case:
class signalable_process : virtual public Process { public: int send_signal(int); };
class POSIX_process : public signalable_process /* , ... */ { // ... };
POSIX_Process p = POSIXProcessFactory::getProcess("some_task"); p.send_signal(42);
Again, this is just an naïve example. While this isn't identical to a real design, it shows how we can reflect capability sets through inheritance. One could imagine having an HPUX class that derived from the POSIX class that presented additional HPUX-specific interfaces.
not a valid example - as signals are pretty much useless on win32. In real code you would most likely need to write: Process p = ProcessFactory::getProcess("some_task"); #ifdef POSIX try { POSIXProcess pp = dynamic_cast<POSIXProcess>(p); p.send_signal(42); } catch (...) {...} #else // WIN32 try { WIN32Process pp = dynamic_cast<WIN32Process>(p); // dont deliver signal as this makes no sense on win32 } catch (...) {...} #endif Now WIN32Process may implement an empty stub for send_signal(), but I'd argue that that idea is flawed.
So that you essentially get objects which will have the same generic / portable' API, but you also get the OS specific functionality.
Exactly! This is exactly what I'm getting at.
Then the programmer decides to use that OS specific functionality. The end result? Non-portable code.
The programmer deliberately chose to use this non-portable functionality, and presumably he knew exactly what he was doing. C++'s type safety leaves very little room for accidents.
agreed - but the actual implementation would most likely need to use dynamic casts.
Why doesn't the programmer simply just the use the native API...? Why bother using Boost at all?
So he can reuse the Boost code. For example, the process class already does 95% of what he needs. He just needs one other feature it's missing.
To me, this is the essence of re-use, and the open-closed principle, and what C++ is supposed to be.
It's very odd to me that you think it would be better to refuse to allow the programmer to trivially extend the class, and instead force him to reimplement the entire process library.
I never said that. And I never said that Boost.Thread should not be extensible. What I said was that certain design decisions were made - if you dont like them, show us the working code. Specifically I am asking that you dont rant, but to explain deficiencies so that they can but fixed. eg: "Hi folks, I have been using Boost.Thread for a while now... I think its API doesn't suite being used as a base class, and here is an example of where it fails... [some example / test-able code] Am I using the API correctly, or is there a deficancy? What do you think?"
Besides that, the ability to extend in this manner leads to a common framework. Unlike the bad kind of framework that tends to lock people out more than anythin else, this kind of framework provides common ground on which people may make separate, but compatible, extensions. For instance, Katie might implement a class that sends signals to a process, and Amy might implement a class that waits for process termination, and Nicole could use both of these capabilities together, on the same process, at the same time. This is reuse.
I dont understand... doesn't:
"A more significant concern is the additional machinery needed to support virtual inheritance."
conflict with:
"I’m not at all worried about making operations that would be normal function calls into virtual function calls..."
In the former quote, I'm talking about the cost associated with doing upcasts with dynamic_cast, and similar operations. In the latter, I'm talking about the simple indirect calls.
For someone who only chooses to use the least common denominator functionality, they will only need to be concerned with the latter sort overhead.
Would you use a library such as Boost.Thread if it had been rewritten in this manner?
No - it is not deterministic.
I don't understand your objection. If you choose to use only the least common denominator functionality, I'd expect the behavior to be, formally and practically, identical to the status quo concrete class.
Nope, not formally, only practically - by making the class polymorphic, you have just added a VTable lookup to function calls (compared with no VTable lookup previously). That said, a VTable lookup wont affect most people. Note that the "No - its not deterministic" refers to not knowing what API a given object will have, without doing dynamic casts (not to the relative number of CPU cycles needed to invoke virtual functions, etc).
However, for those users who need features only availible in some environments, they are free to re-use the existing code, plus add their own code to support their particular use-case.
I can't beleive that it is better to *not* give a user a chance at extending the class.
Again, I never said that.
Thank you for the detailed analysis and criticism,
No problem, Mathew

"Aaron W. LaFramboise" <aaronrabiddog51@aaronwl.com> wrote in message news:422BAE65.4090608@aaronwl.com... Over the past few years, the style of our abstract interfaces for system-dependent features has bothered me more and more. Here’s my challenge to those happy with the status quo: Find me an interface in Boost or the C++ standard that represents an observable inter-program interface, such that, for any popular operating system, I can’t find a feature of the operating system that is impossible to express using that interface without relying on undefined behavior, undocumented behavior, or circumventing access specifiers.
[...]
So here’s my question to the Boost community. How many people have similar concerns and experiences? How often, in real code, do concrete classes prove insufficient? Who here has to entirely reimplement libraries like Boost.Threads for relatively silly reasons? Are there any alternate solutions for the problem I describe? What unforeseen problems might there be with this polymorphic style? Would you use a library such as Boost.Thread if it had been rewritten in this manner?
I'd guess that most people using e.g. Boost.Thread (and have been using native threads previously) feel limited by the exposed interface. There's still "nothing" you can't do by getting to the current native thread handle/id and using the native API to do what you wan't to do - providing that you are willing to put in some additional work and/or drop portability in your code. That aside, I've had similar thoughts myself, but I was more into CT polymorphism (using policy classes to expose additional functionality). Some pseudo-code would be: template<typename T> class portable_threading_api { ... }; template<typename T> class win32_threading_api { public: bool exit_thread(DWORD errorCode) // make virtual? { return (FALSE != ::TerminateThread(static_cast<T*>(this)->native_thread_handle(), errorCode)); } int set_thread_priority(...); }; template<typename T> class posix_threading_api { ... }; template<typename ThreadingApi = portable_threading_api> class thread : public ThreadingApi<thread> { ... }; --- Using the above, people wanting to write portable code could limit themselves to use thread<>, people needing additional Win32-specific functionality could use thread<win32_threading_api>, etc. As more features were portably implemented, people could move from platform-specific to portable implementations (if they would like to). Just some additional food for thought. Regards, Johan

Hi Aaron, Just a quick comment: while your complaint is a reasonable and relatively common one, and while I think your proposal for introducing more granularity by way of deeper inheritance and factory methods is well-reasoned, there are two issues that still prevent me from agreeing with your approach. The first is the attitude of "a user shouldn't have to pay (too much) for what she's not using." That's a far cry from saying "a user shouldn't have to pay AT ALL for what she's not using" because, in my opinion, you're gambling that for all domains to which your proposal applies -- not just threads, but file IO and so forth -- the penalty you pay for virtual pointer indirection at a minimum, and perhaps the dynamic_cast<> or typeid() operations, or even exception handling, is negligible in comparison to the performance cost of the abstracted operating system functionality itself. You're furthermore gambling that RTTI or exceptions are even available (or, if available, appropriate) to use on all platforms: while admittedly embedded systems programmers may already have only limited use for Boost, I'd hesitate to add this additional prohibition. My second issue with your proposal is the coding style it demands. Have you had the joyous experience of working with COM? You're essentially proposing a QueryInterface work-alike. But the legalese and boilerplate code required for even the simplest of tasks in COM is, in my opinion, entirely anathema to the elegance of Boost's libraries (particularly boost::thread, which I felt was a godsend when I first encountered it!). And whether you choose to implement your QueryInterface in a way that selects implementations at compile time or at runtime, developers using your implementation run into one of two typical code-clutter issues: If the selection is done at compile time, developers of cross-platform code must set up a series of #ifdefs that are perfectly in sync with the functionality Boost would have elected to provide on each platform. If the selection is done at runtime, you get into the business of providing GUIDs to identify interface and implementation versions at best, or forcing use of RTTI at worst, which reflects back to the developers as unnecessarily complicated client code. So as much as I'd like to see better representation of non-portable features in Boost, this is a problem whose solution I still consider fleeting. Cheers, dr On Sun, 06 Mar 2005 19:29:09 -0600, Aaron W. LaFramboise <aaronrabiddog51@aaronwl.com> wrote:
Over the past few years, the style of our abstract interfaces for system-dependent features has bothered me more and more. [snip]

Aaron W. LaFramboise wrote:
[...] So heres my question to the Boost community. How many people have similar concerns and experiences? How often, in real code, do
We face similar questions in the design of the socket library. There are basically two I/O models: synchronous and asynchronous read-/write-methods. The asynchronous I/O model can be implemented quite differently using multiplexing, aio, I/O completion ports, threads etc. Now someone said he wants to have some sort of control about how the asynchronous I/O model works eg. by specifying the number of background threads. Clearly that would be only possible if the asynchronous I/O model is based on threads. Currently I assume there are just two classes synchronous and asynchronous but I can think of lots of other classes that might be added later and inherit from asynchronous. Your posting reminds me a bit of the compliance levels in UML 2.0. Everything in UML 2.0 belongs either to Basic (level 1), Intermediate (level 2) or Complete (level 3). In order to compare capabilities of UML tools there is either no compliance, partial compliance, compliant compliance or interchange compliance. It's more complicated than a simple black and white but a tribute to the fact that black and white just doesn't exist in reality. I don't know about Boost.Thread but I understand that some library users would like to know how an asynchronous I/O model works and what they can do about it. Boris
participants (14)
-
Aaron W. LaFramboise
-
Angus Leeming
-
Bob Bell
-
Boris
-
Dan Rosen
-
David Abrahams
-
Douglas Gregor
-
Felipe Magno de Almeida
-
Johan Nilsson
-
John Torjo
-
Mathew Robertson
-
Rob Stewart
-
Robert Mathews
-
Tony Juricic