Cpp Heresy

I've been thinking about ExtremeFormsForCppCode and am presently trying a little experiment. The details of the experiment are as follows, critical comment invited:

  1. AlternateHardAndSoftLayers. Okay, that's not heresy, but it enables the heresy to come. Presently using PythonCxxModule to achieve this while still keeping good StlStyle.
  2. Use automated UnitTesting by bolting the PythonCxxModule stuff to PythonUnit.
  3. Use Python to break dependencies between Cpp classes. In fact, begin with python and only refactor to C++ when performance critical profiling or an API UserStory results in a real need. In other words, DoAllYourEncapsulationInTheSoftLanguage?.
  4. Now, since we're breaking header dependencies and separating our encapsulation, we don't need to worry about pushing C++ dependencies into implementation files. Ergo we can ForgetAboutWritingAccessors. Yes, that's right, leave your data members public. If you're XP then your UnitTests will allow you to ReFactor just fine without worrying too much about forgetting type dependencies. The compiler will only help you get this right.
  5. If you're going to go that far, heck, leave everything public and UseStructsNotClasses. No, not C-style structs, these are structs with methods - they're just classes with everything public by default.
  6. Let's see, what else is a mess. Oh yes, there's all that drivel where we compose a container and have a method that just calls its methods. Screw that - InheritStlContainersPublicly. Oh, you think that will break encapsulation? BloodyOath it would, except that python is doing your encapsulation for you. Well then, what about when you have a class with two containers? ReFactor it. But wouldn't that result in MI? Maybe not with StlStyle, but even if it does, MultipleInheritanceIsNotEvil.
  7. Okay, what's left that's disgusting in cpp? Oh yes, having to write bloody implementation files. Okay then InlineAllMethodsWhereverPossible. What, your method is too long or complex to do that? Then go ReFactor the sucker - ReFactor any method that's longer than 20 lines, and most that are longer than 10. Oh, and you're worried about compile dependencies getting too big? Don't you remember how PythonCxxModule broke them for you?
  8. Be sure to AvoidConstCompletely. Use it when you have to--to interface with libraries that insist on returning a const Foo* or const Bar&. JavaLanguage, PythonLanguage, and numerous other OO languages don't have "const" (final is a different beast) and get by fine--besides, ConstIsaVirus.

The result is C++ code that looks strangely like python code. It's all OnceAndOnlyOnce. Seems to run just dandy and so far works as advertised. No, I haven't tried this on a large scale yet - just with a couple dozen classes - but I don't see any harm in it, and obviously there's a lot of benefit in OnceAndOnlyOnce. Of course this heresy is breaking almost every rule in every C++ and OO book ... but then TheyreJustRules ... --PeterMerel

There are methods in his madness. --LamontCranston

No: he puts the methods in his structs.

What are you getting from C++? If you don't want to use C++ for templates or OO, just use C. -- SunirShah

Years later, with seven or more supported compilers, and dozens of HandHelds under thumb... I understand completely now. Peter is my hero. I actually yell at people when they use constructors. -- Sunir "Objects are for idiots" Shah

Reasons not to use C:

  1. No ctors and dtors.
  2. No methods.
  3. No STL.
  4. Dangerous as hell. No good way to prevent memory corruption or thread collisions. No civilized exception handing. And the list goes on and on.
  5. Complete mismatch with the PythonCxxModule
  6. Come on Sunir, we're civilized ladies and gents around here, even when we're heretics. C is a language for barbarians and kernel hackers, not dainty large-scale systems folk like us.

-- PM

Dainty? Be a "real programmer" and program your own Python object model in C++. I did it. It was fun and easy. Just snap together as many GoF patterns as you can with ATL-style templated multiple inheritance. Debugging slots that are really objectified wrappers (just to allow AspectOrientedProgramming) that reside in a recursive hash table data structure is an adventure in job security. You'll love it! -- ss

I can see I don't have a lock on the CppHeresy around here ;-) --PM

By the way, I really did do that. If they had given me one day and one good reason, I could have implemented Smalltalk's #become. (Shut up; I had just read the GangOfFour book the week before!) --ss

Actually, if I read you right, I believe I've done pretty much the same thing in the past - at least I reimplemented Perl/Python composite builtins + literals as C++ classes. But that's a different solution to a different problem in a different context to this particular CppHeresy. Here we're trying to make C++ more maintainable and easier to ReFactor, not just use Python types per se. --PM

I think it's relevant. Either you go through all the work to implement a dynamic layer in C++ yourself - which can be death if you don't grok the language (*) - or you use a dynamic language on top of C++ (AlternateHardAndSoftLayers). It's a BigDesignUpFront decision to use a secondary language, though. Microiterative design leads to the C++ cake. That's what happened to me.

Picking the right tools makes the job easier.

Question: what kinds of work are you doing in C++ that requires the ++ part? Why are doing all that stuff that is hard to write in C? Use the higher level language for that.

When I optimize to C++ I don't want spaghetti. Plus read again the C objections above.

(*) I used every keyword in the language to build my object model except "auto" and "register" (which are useless in MSVC++). Yes, I used "volatile" too. Messed up, neh? --ss

You want messed up, see the use of "explicit" below :-P --PM

Which does what for you, exactly? --ss

If you are asking what "explicit" does: It tells the compiler not to use the constructor for conversion, only use the constructor if the code "explicit"ly invokes it. In other words, if you define a constructor that takes an int (and do not use the explicit keyword), the compiler will automatically convert ints to your class when necessary. This might not be what you want.

As to why it is being used in the example below, I'm still not quite sure... --BillDavis

(Probably because of a rule -- consciously or unconsciously obeyed -- that says "always use explicit when you write a constructor unless there's a good reason to the contrary". Possibly the rule obtained from this by replacing "constructor" with "single-argument constructor" would be a better one, but simplicity has value...)

That's a really bad rule. explicit exists to help you not fight the type system when necessary, but overdoing it will just make you fight it in another direction. -- SunirShah

I think you may not have noticed the words "unless there's a good reason to the contrary". I'd consider "because I want integers to be converted to bignums automatically when I mix them" to be a pretty good reason for not making Bignum::Bignum(int) explicit, for instance. But "because that's what the language does and I didn't think about it" wouldn't be a good reason, and that's probably the commonest reason for constructors not being made explicit. :-)

I haven't found many occasions when using explicit was the right thing to do. Under what circumstances do you think explicit helps you rather than hinders you? -- SunirShah

It was used here because the ctor was getting invoked by a totally unrelated streaming operator. Most unseemly and the only occasion, in more than a decade with the language, that I've ever had occasion to use it. I certainly use no such rule as the one posited - or at least it isn't part of the CppHeresy. --PM

It's a BigDesignUpFront (BDUF) decision to use a secondary language, though. Microiterative design leads to the C++ cake.

Very interesting to call this a BDUF decision. I know the kind of thing you're talking about (he thinks). But I would never call it BDUF. One of those MinimalDecisionsUpFront? that was crucial (or was it?). An architectural decision? (Grimace). ChooseYourRutCarefully? Can microiterative design ever be wrong?

Well, it's design up front at any rate. Yes, MicroiterativeDesign can be wrong because it is only locally optimizing. To find the global maximum, you have to make some non-micro design decisions.

Design up front is not the same as BigDesignUpFront. Choosing to use a secondary language is no more Big Design than choosing which primary language to use is. Big Design Up Front is when you make a big set of decisions before you actually start coding -- what all the classes will be, what the methods on them will be, etc. . Then, during coding, either you don't take advantage of new information you discover (either about the code or about the changing requirements), or you do take advantage of it, and consequently toss out lots of your up-front Big Design. If, as the ExtremeProgramming guys claim, software can be kept cheap to change by refactoring and other practices, then BigDesignUpFront is a losing bet.

Why is MicroiterativeDesign only locally optimizing? Or are all global optimizations not microiterative by definition?

Why is a choice of second programming language "Up Front" at all? Surely a second programming language can be added to a project as easily as any medium-sized third-party code library? What are programming languages, if not producers and consumers of code libraries?

Why couldn't a program be microiteratively (re)designed from being primarily written in language A to being primarily written in language B? It should be just like any other refactoring.

(Not really meaning to be nasty but...) If Python is such a great solution to all your OO problems, why don't you just write the entire application in Python? If some parts suffer from actual measurable performance problems, you can always rewrite those parts as C++ components, to be used by Python. -- JeffGrigg

Um, yeah, see AlternateHardAndSoftLayers, first word, first point, top of page. -- PeterMerel

Yes, but if you DoAllYourEncapsulationInTheSoftLanguage? (with emphasis on the "ALL" part), then aren't you really doing practically all the application programming in the soft language? I can understand using assembly/C/C++ to build high performance low level tools/components, when needed. But wouldn't it be simpler to do all the higher levels in Python instead of a mix? What benefits to you get by wedging C++ between your Python modules? (Yes, I guess I am challenging AlternateHardAndSoftLayers.) -- JeffGrigg

I suspect we're in ViolentAgreement. Yep, do all the higher levels in Python. Then when you profile and discover you need something to run fast, go to CppHeresy. The intent is that CppHeresy provides guidelines about how to keep the C++ bits simple.

1-3 is not heresy at all, it starts at 4. Now if only PythonCxxModule would not require a real brand-new gcc, we would even use it (not necessarily to be heretic ;). -- JuergenHermann

I'd like to see the code ... -- RonJeffries

I'm still futzing with the PythonCxxModule part of this so won't paste that yet, but here's a sample from a heretical header. Apologies for obfuscation:

 struct oFooMap
    public map< oFooAddress, Ref<Foo> >,
    public oFooSomeOtherBaseThingy
    typedef map< oFooAddress, Ref<Foo> >    base_type;
    typedef base_type::key_type             key_type;
    typedef base_type::data_type            data_type;
    typedef base_type::value_type           value_type;
    typedef base_type::iterator             iterator;


oBletchSet & m_input; ulong m_inputSize;

// = METHODS //

explicit oFooMap(oBletchSet & input, ulong size) : m_input(input), m_inputSize(size) { ASSERT(m_input.size() > 1);

sort ( input.begin(), input.begin() + m_inputSize );

// ...

addFoo ( oFooAddress::bar(), m_inputSize / 2, m_inputSize ); }

~oFooMap() {}

void addFoo ( oFooAddress const & fa, ulong refPoint, ulong size ) { // Note Foo ctor will recurse ... // insert(make_pair ( fa, new Foo ( refPoint, size, *this ) )); }

Ref<Foo> operator[](oFooAddress const & fa) { Ref<Foo> foo = find(fa).second; ASSERT(foo);

return foo; }

Bar testRow ( oBletchSet::Row const & row, ulong testIndex ) { Ref<Foo> bar = operator[](oFooAddress::bar()); ASSERT(bar); return bar->test(row, testIndex); }

friend ostream & operator<<(ostream & o, oFooMap const & fm);

}; inline ostream & operator<<(ostream & o, oFooMap::value_type const & v) { return o << " Foo(" << v.first << "): " << *(v.second) << endl; }

inline ostream & operator<<(ostream & o, oFooMap const & fm) { o << " oFooMap size " << fm.size() << endl;

copy ( fm.begin(), fm.end(), ostream_iterator< oFooMap::value_type >(o, "\n") );

o << "--" << endl;

return o; }


I'm not brave enough to look at the code, but some of this looks helpful for JPython, Java and JUnit, Peter. How heretical could you be (or would you like to be) with that combo? -- RichardDrake

Haven't tried JPython for a long time so wouldn't like to say for sure, but some of the above might apply. OTOH, with Java's lack of adequate collection classes and multiple inheritance, maybe not ... --PeterMerel
Choosing to use a secondary language is no more Big Design than choosing which primary language to use is.

That is to say that it is an extremely big decision. Hammer -> Nail and all that. Choose perl? Smalltalk? VisualBasic? Html?

You've already made an enormous difference to what people will expect from your creation.


But that's in a different category than BigDesignUpFront stuff, isn't it? I mean, you look at your goal and say "well, Smalltalk is probably better for this" or maybe you pick perl, and then you code and by coding you work out that your architecture feels like this and like that... Whereas if you're doing BigDesignUpFront you're choosing your architecture first.

And the language absolutely decides a lot of what can and can't be done architecturally--languages may be TuringEquivalent but they're expressive in different ways--but it seems to me that it's like saying "I want to walk to the first McDonalds on Tecumseh St." and then picking a direction to start in, rather than trying to map out every step of the route only to find halfway there that traffic on another route was lighter, and McD's is closed by the time you get there.

-- RobRix

Hmm, what if you work on a large project which C++ is suitable for, and one day you find yourself facing the need to differentiate (or somehow else trasform) a symbolic formulae? Specially if you know the task can be completed in Lisp in 30 minutes while C++ will take no less than a day, and there will be enough such tasks so that it cannot be ignored, but still not enough to switch to Lisp completely. I mean, it there really a reason to reject the idea of having secondary languages in a project, at least for such minor subtasks? -- AndreyStolyarov

CategoryCpp. See WindowsTemplateLibrary, StandardTemplateLibrary, CppOrthodoxy

View edit of April 2, 2008 or FindPage with title or text search