Recently, I was trying to debug some code on my weekend spare time, to help out a former employer who needs a bug fixed in a C++ application. I removed an ad-hoc shared memory implementation previously, and substituted a well known and trusted shared memory implementation, called Boost::Interprocess.
I really don't see a reasonable alternative to Boost::Interprocess for C++ shared memory, but I really haven't gained any love for C++ during this exercise. In fact, my antipathy towards C++ has grown towards what you might call full blown hatred of Modern Boost::style::C++. I think that I would like to make a T-Shirt that says....
Is that a ...
boost::intrusive::detail::tree_algorithms<
boost::intrusive::rbtree_node_traits<boost::interprocess::offset_ptr<void,int,unsigned int,0>,1> >::find<
int,boost::intrusive::detail::key_nodeptr_comp<boost::container::container_detail::rbtree<int,std::pair<int const ,DATAPOINT>,
boost::container::container_detail::select1st<std::pair<int const ,DATAPOINT> >,std::less<int>,boost::interprocess::allocator<std::pair<int const ,DATAPOINT>,
boost::interprocess::segment_manager<char,boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,boost::interprocess::offset_ptr<void,int,unsigned int,0>,0>,
boost::interprocess::iset_index> > >::key_node_compare<boost::container::container_detail::tree_value_compare<int,std::pair<int const ,DATAPOINT>,std::less<int>,
boost::container::container_detail::select1st<std::pair<int const ,DATAPOINT> > > >,
boost::intrusive::rbtree_impl<boost::intrusive::setopt<boost::intrusive::detail::base_hook_traits<boost::container::container_detail::rbtree_node<std::pair<int const ,DATAPOINT>,
boost::interprocess::offset_ptr<void,int,unsigned int,0> >,boost::intrusive::rbtree_node_traits<boost::interprocess::offset_ptr<void,int,unsigned int,0>,1>,0,
boost::intrusive::default_tag,3>,boost::container::container_detail::node_compare<boost::container::container_detail::tree_value_compare<int,std::pair<int const ,DATAPOINT>,
std::less<int>,boost::container::container_detail::select1st<std::pair<int const ,DATAPOINT> > >,boost::container::container_detail::rbtree_node<std::pair<int const ,DATAPOINT>,
boost::interprocess::offset_ptr<void,int,unsigned int,0> > >,unsigned int,1> > > >(const boost::interprocess::offset_ptr<boost::intrusive::compact_rbtree_node<boost::interprocess::offset_ptr<void,int,unsigned int,0> > const ,
int,unsigned int,0> & header={...}, const int & key=3, boost::intrusive::detail::key_nodeptr_comp<boost::container::container_detail::rbtree<int,std::pair<int const ,DATAPOINT>,
boost::container::container_detail::select1st<std::pair<int const ,DATAPOINT> >,std::less<int>,boost::interprocess::allocator<std::pair<int const ,DATAPOINT>,
boost::interprocess::segment_manager<char,boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,boost::interprocess::offset_ptr<void,int,unsigned int,0>,0>,
boost::interprocess::iset_index> > >::key_node_compare<boost::container::container_detail::tree_value_compare<int,std::pair<int const ,DATAPOINT>,std::less<int>,
boost::container::container_detail::select1st<std::pair<int const ,DATAPOINT> > > >,
boost::intrusive::rbtree_impl<boost::intrusive::setopt<boost::intrusive::detail::base_hook_traits<boost::container::container_detail::rbtree_node<std::pair<int const ,DATAPOINT>,
boost::interprocess::offset_ptr<void,int,unsigned int,0> >,boost::intrusive::rbtree_node_traits<boost::interprocess::offset_ptr<void,int,unsigned i comp={...})
... in your call stack or are you just happy to see me?
How would you feel debugging a line of call-stack items of the above level of complexity, where the above is simply 1 of about 20 similar levels of function calls. Why even print that crap, we may as well go back to debugging via memory addreses in the EXE file, and naming these things checkpoint Fred, checkpoint Charlie and checkpoint Dave.
Anyways, before you go absolutely crazy with Spring4D and try to create an equivalent mess in Delphi now that we have generics which are a lot less powerful than modern C++ templates, but still powerful enough to create a lot of trouble, ask yourself, do you really really want to debug a call stack that has a few dozen TFactoryProxy<
TDependencyInversionContainer<
TRedBlackTreeImplementation,
TMyFactoryProxyClassBeanMetaClass>>>
expressions when you break it?
What a great example, I too am grateful that Delphi isn't C++
ReplyDeleteWarren, C++ has never really gotten the love of developers and lately there's been a hunt going on for what will ultimately replace it (much as how some people are excited at the idea that Google's Go, intended for system programming, could replace C). Have you checked out "D"?
ReplyDeletehttp://dlang.org/
Type inference, automatic memory management, array slicing, design-by-contract... it certainly checks off a lot of boxes.
Per your last comment, I'm more worried about the Java-ization of Delphi than turning it into C++. I've seen certain popular Delphi blogs go crazy about "Design Patterns" that were designed for Java, some of which don't even make sense in Delphi or any other language with first-class functions.
Every language has design patterns because every language falls short in some regard. IMHO, design patterns are simply commonly recognized solutions to a languages shortcomings. The original GoF book used C++ so the patterns worked around C++'s shortcomings. Java shares some of these shortcomings and introduces some of its own.
DeleteDelphi also shares some similar shortcomings with C++, Java and C# because it's inheritance model and type system are similar. For instance the observer pattern is a common way to handle multicast events. C# supports multicast events through its support for assigning multiple event handlers to the same event. Delphi, C++ and Java do not have this facility so the observer pattern steps in to fill this role. Coincidentally, if you look under the hood of C# to see how multicast events are implemented you'll find it is the observer pattern wrapped in syntactic sugar.
So the problem isn't the "Java-ization" of Delphi. The problem is that no one has really taken the time to understand which design patterns are appropriate for Delphi and/or invent ones specific to Delphi. To be fair, the languages these patterns were created for are plagued with overuse/misuse of the patterns. So its not so much that patterns invented for one language aren't useful in another but that they are often applied incorrectly across the board.
This thing of solving a problem and then the solution becomes a new problem is one of the thorny issues that software developers, regardless of language, platform, or libraries, always seem to create.
DeleteFind problem, propose solution, and then implement solution widely, then discover after it's everywhere that it introduces new problems, some just as bad as the one you solved.
I'm not saying to give up and not even try, but rather, that triumphalism and the tendency to create and worship silver-bullet solutions is something we always need to remain aware of as innate tendencies within us.
Only then, after we observe the principle "temet nosce", are we free to innovate.
Design patterns (mostly the GoF ones) and other principles apply to almost every (OOP) language - the point is that their implementation might differ depending on what features the language offers.
DeleteFor example the before mentioned observer pattern: in Delphi versions that have generics support (Delphi 2010 and higher - 2009 was more broken than anything) you can use multicast events like Spring4d or DSharp offer pretty much like any normal event.
Erm, I'd have had a code review throw that C++ programmer from a large height with a short rope tied to any extremity, rather than blame the language or even blame boost - I do admit boost has horrible syntax, but in this case the original implementer has not helped on the PR side of things, not using typedefs with boost is positively anti-social IMO.
ReplyDeleteBoost isn't one library, it's a set of libraries, and each library varies in implementation, but at its heart all the Boost libraries seem to follow two rules (a) be modern (use templates everywhere, for everything), (c) for purely templated code, be mostly a header library.
DeleteSo, if you're going to compose 19 layers of stuff, you're going to end up with messes like the one I posted above. There ain't no easy solution. If you start down the road Boost started down, this is where it leads.
Delphi generics aren't 1% as powerful as C++ Templates are, and that's a GOOD THING, because this is power to shoot yourself in the foot in a whole new set of ways. I think it was Bjarne Stroustrup himself who said that C is a pistol that you can shoot yourself in the foot with, and C++ is a machine gun that can take your whole leg off.
W
In my latest code revision I wrote typedefs for all these things. The base type I named Bob. Short for Bob the Builder.
ReplyDelete:-)
I wonder if you actually have ever used Spring4d - judging from your pseudo code I guess no.
ReplyDeleteYes, nested generics can get messy - especially if the classnames get longer but if you name them properly there is no difficulty in reading them. Anyway there won't be much nested generics when you debug the Spring4d DI container source.
I think the compiler would break with an internal AV before we got anywhere close to boost level complexity. It's the general march to solve one kind of complexity (such as coupling) that creates another kind, that is my general point. There ain't no free lunch, and fighting coupling with D.I. isn't magic, isn't perfect, and isn't even always an improvement for everyone everywhere. SOMETIMES it is. It's that 100% yes/100% no binary thinking that I'm trying to combat here. No insult to Spring4D intended. I have not used Spring4D "in production code", only played around with it.
ReplyDeleteThe compiler breaks with some ICE pretty quick when using generics combined with anonymous methods - especially during development with constant compiling/changing. I am totally with you (I am still dreaming of language integrated coroutines in delphi like in boost). And I am dreaming of the possibility of building rich generic classes without having the binary size or the compile time explode because some compiler guy does not know what he is doing.
DeleteI agree that DI is not the solution to everything especially not using a container to do so (though everyone is doing DI every day when passing one thing into another) - and understanding the very basics of DI and loose coupling often leads to better code. Which should not be confused with overengineered solutions some people come around with. DI can be very simple and straight to the point.
@Stefan; Perfectly said, thank you!
DeleteI'm stuck with C++ and I must say, I hate it, but I have no choice. It's exactly as you wrote. I have the feeling ever since Turbo Pascal things are becoming more complicated because everybody has fallen for a collective madness where they believe more complicated equals progress. Except for a few things, I don't need any of that complicated C++ crap in my daily work, but what can I do :(
ReplyDeleteI tried Ada but the Adalibre IDE is a mess and the GDB debugger is a torture. I'm looking into Delphi as well maybe for the future. I wish there was an innovative compiler designer out there like Borland that makes compiles which are actually useful and bugfree