It was a summer workday much like any other. The air conditioning was laboring to keep up, but it was still pretty hot in the office. I fought the soporific effects of a heavier-than-usual lunch while working on a piece of new code. My keyboard clicked and clacked softly. A fly buzzed against a nearby windowpane. Otherwise, everything was quiet.
Everything was quiet, that is, until suddenly Bob walked into my cubicle, slapped a piece of paper on my desk, growled the words Fix it at me, and left before I could even form a response. Puzzled, I picked up the paper. I had barely had a chance to realize that it was a photocopy of a page from our coding standards before I was interrupted by Wendys voice: What was that smack?
I gophered up and saw her. Bob, I said, and held up the page. He came by and
Ssssh! Wendy held a finger to her lips and ungophered, and then came around to join me in my cubicle. Is that a page from the coding standards? she asked, her voice low.
Yes. I
She rubbed her hands together gleefully, more gleefully than I could recall ever having seen her before. Oh, he must be just burning! This is great. Ill bet this is because I did the same thing to him yesterday.
Did what?
Smacked him with a page from our coding standards... well, four pages actually, and in front of the interns... and I could tell he was just so mad that I embarrassed him like that. He must have been dying for an excuse to turn around and do it again to someone else. So show me, show me! What did he have to criticize?
I held up the sheet. The bold heading announced: Ensure that header files are self-sufficient. Along the side, untidily scrawled in Bobscript 22 point (varying to 8 point for some letters) was the name of a file Id checked in last week. I deciphered the Bobscript and pulled up the file that got me in trouble. Simplifying the names and eliding nonessentials, it looked something like this:
// --- file x.h ------------------------ // #ifndef MYPROJ_X_H #define MYPROJ_X_H #include "a.h" #include "b.h" class X : public B { // ... private: std::vector<A> v_; }; #endif
Aha! Wendy said after a moment.
Aha? I ahad.
She pointed an accusing finger at the screen. Fraid hes got you dead to rights this time, pardner, she said, and started fanning herself with a sheaf of papers she picked up from my desk. The air conditioning seemed to be losing ground.
He does? But I included the base class definitions, and the... oh. Its vector.
She nodded. Right.
I guess I didnt notice the problem at first because it worked fine in my test harness, I admitted. It just happened that all the files I tried it with also included vector for other reasons. And a.h and b.h dont include vector, so I ought to include it myself.
vector is a very common #include, Wendy agreed. Most of your callers probably wouldnt notice.
A book snapped shut behind us, but we were too lethargic with the heat to give much of a jump. And the solution? said a quiet voice behind us.
Hi, we greeted the Guru. I said, I guess the simplest thing would be to just make x.h also include the standard header vector.
The Guru inclined her head. The simplest thing, she agreed, and the most correct, yes. Even if a.h or b.h included vector already, it would not hurt you to consider including it for completeness, as the prophet Lakos recommended [1]. In the event of a future revision or refactoring of A or B that might cause a.h or b.h to no longer include vector, your header would still be self-sufficient. She pushed a graying lock behind one ear. Of course, she added, including vector is not the only possible solution in this case.
I thought about that. I guess I could Pimpl out the private vector member, I decided [2]. That would let me avoid including vector, and it would let me stop including a.h too.
Indeed, said the Guru, while Wendy continued to fan herself. The Pimpl idiom... it is worth using here? What say you?
I dont think so, I shrugged. I know that header vector never changes... or at least, if it does, were upgrading the compiler and that means we have to rebuild the world anyway. And I know that a.h is a small and stable header that hasnt changed for years. So, no, I dont think its worth it.
The Guru nodded. Well said.
I shook my head as I made the change to add the missing line. It burns me that Bob was able to catch me like that.
Aw, cheer up, pardner, Wendy offered. Bob was just looking for a reason to burn someone. Just make sure you dont let him burn you again for the same thing.
Such as in your current code... the Guru added innocently, opened her tome, and stood there pretending to read it. Wendy continued fanning herself and studied the ceiling.
The silence hung heavily in the air.
The seconds ticked by quietly. A distant drip, drip, drip from the air conditioning unit could now be heard. The fly had stopped buzzing against the nearby window and was now walking on my screen. I shooed it away, but it immediately alighted again a few feet away on my desk. The fly was tired too.
I cleared my throat dramatically. Okay, so heres the header I was just working on, I announced and went back to that file. Again simplifying the names and eliding nonessentials, it looked like this:
// --- file y.h --------------------------------- // #ifndef MYPROJ_Y_H #define MYPROJ_Y_H class Y { // ... public: template<typename T> void f( T& t ) { Expensive ex; // ... do work with ex ... } }; #endif
Ah.
The Guru looked up from her tome. Wendys fanning motion sped up slightly.
Well, I welled a little defensively, I guess I dont include the header that has the definition of Expensive, but I did it that way on purpose, you know. Most users of Y will never care. That type only appears in the implementation of one of my member template functions. And that wont get instantiated unless someone uses it, right?
Apprentice? the Guru said, indicating Wendy.
Uh... Wendy said, surprised at being drawn into the conversation. Uh, hes right, if a caller never uses Y::f, there shouldnt be a problem, it should never be instantiated.
Correct, the Guru said, and turned back to me. If it were private, you could Pimpl it, but it is not. Have you considered delivering f as a nonmember, and perhaps non-friend, function? She took the keyboard and rearranged the code slightly, then split it into two files:
// --- file y.h --------------------------------- // #ifndef MYPROJ_Y_H #define MYPROJ_Y_H class Y { // ... }; #endif // --- file yutil.h ----------------------------- // #ifndef MYPROJ_YUTIL_H #define MYPROJ_YUTIL_H #include "y.h" #include "expensive.h" template<typename T> void f( Y& y, T& t ) { Expensive ex; // ... do work with ex ... } #endif
There, she said, and stepped back. What do you think of that?
I like it, Wendy threw in before I could answer. Now both headers are standalone.
I saw it too. And still only the users who need f will incur the cost of including Expensives definition. Sweet. Say, I added, didnt someone write an article about other benefits of implementing functions as nonmembers?
Indeed, the Guru acknowledged. The prophet Meyers is one who has had the courage to do so. [3] In particular, he demonstrates persuasively... one might even say, effectively... why nonmembers that are also non-friends increase encapsulation. This is another demonstration of the flexibility of nonmember functions, particularly given that C++ does not support extensible classes that can be reopened like namespaces and defined across multiple header files.
Thats all well and good, Wendy challenged, but what if the situations just a little different? She wrote:
// --- file Z.h --------------------------------- // #ifndef MYPROJ_Z_H #define MYPROJ_Z_H template<typename T> class Z { // ... public: void f( T& t ) { Expensive ex; // ... do work with ex ... } }; #endif
Wendy stepped back: Now, instead of a member template function, we have a plain member function in a template. What about that?
The Guru nodded. Prithee tell: When is f instantiated?
Wendy opened her mouth, then thought better of it and closed it again. The Guru looked at me and lifted one eyebrow, but I could only give my best deer-in-the-headlights look.
My apprentices, the Guru said, member functions of templates are instantiated only if used. At least, that is the case on conforming compilers, and in this respect fortunately the compilers we use are all obedient to the Holy Standard. In this case, only when a user of Z<SomeType> uses f, will Z<SomeType>::f be instantiated for that user. Thus this case is the same... yes, only trivially different it is. And she wrote:
// --- file Z.h ------------------------ // #ifndef MYPROJ_Z_H #define MYPROJ_Z_H template<typename T> class Z { // ... }; #endif // --- file zutil.h -------------------- // #ifndef MYPROJ_ZUTIL_H #define MYPROJ_ZUTIL_H #include "expensive.h" template<typename T> void f( Z<T>& z, T& t ) { Expensive ex; // ... do work with ex ... } #endif
She made as if to leave, but then turned back to us. Oh, and we will be adding a new step to our build system. Every header file header.h will be tested to ensure it is a compilable standalone by attempting to build a file like this. She wrote on the whiteboard:
#include "header.h" int main() { }
Only your original x.h file would have failed this test, she added considerately. Your y.h, even had you not amended your writings, would have passed it. She bowed quietly, and turned, and began walking away down the hallway.
Just before she passed around the corner, we heard her voice drift back: I hear there is a game this week in the coffee room to guess what percentage of Bobs headers will pass it...
References
[1] J. Lakos. Large-Scale C++ Software Design. Addison-Wesley, 1996.
[2] H. Sutter. Exceptional C++. Addison-Wesley, 2000.
[3] S. Meyers. How Non-Member Functions Improve Encapsulation. C/C++ Users Journal 18, no.2 (February 2000): 44-52.
Herb Sutter (www.gotw.ca) is convener of the ISO C++ standards committee, author of the acclaimed books Exceptional C++ and More Exceptional C++, and one of the instructors of The C++ Seminar (www.gotw.ca/cpp_semina). In addition to his independent writing and consulting, he is also C++ community liaison for Microsoft.
Jim Hyslop is a senior software designer with over 10 years programming experience in C and C++. Jim works at Leitch Technology International Inc., where he deals with a variety of applications, ranging from embedded applications to Windows programs. He can be reached at [email protected].