Use Structs Not Classes

Part of the CppHeresy.

One should only use structs for POD (PlainOldData) types that have no methods and whose data members are all public. Even then, why bother, just use classes and then you only have to remember one set of rules. Why be different just to be shocking?

I use "class", but it doesn't seem like a big deal either way. It's just a matter of convention, which programmers should be able to get used to.

"Struct" is arguable better in that it defaults to public inheritance (which is the most common kind) and public access (which is what you want if you list the public interface first). Using "class" you typically have to insert the keyword "public" in two places, for no real advantage. It's not a big deal, but other things being equal we might as well make our conventions short.

"Class" as a keyword should never have been added to C++, in my view. It's a needless complication. -- DaveHarris

Well, there is this notion of having to explicitly publish behavior which has a nice smell to it. This way, you have to reveal implementation on purpose which is somehow sounds much better than having to explicitly protect implementation. -- RobertDiFalco

I read an interview with Ritchie in which he said he wished he had taken that approach to C modules. E.g. all symbols in a file default to static linkage and the programmer has to explicitly declare which are external linkage.


Sunir discusses (exaggerates?) how embedded systems have changed his coding practices.

Because I was a good object-oriented dweeb, I began writing all my aggregate types as

     class Class
     {
     public:
         Class();
         ...and optionally by the RuleOfThree...
         virtual ~Class();
         Class( Class const & );
         Class &operator =( Class const & );
     protected:
     private:
     };

However, the more I understood what it meant to write a simple program, the more I ended up changing all of these to be

     struct Class
     {
     };

This helps route out the Java programmers too. They usually kick and scream that I've added methods to structs as if that didn't compile! I throw those people out of the room. Java programmers are hopeless.

I've also learnt that constructors are stupid. They make things complicated, and they bloat your application. Instead, set the members by hand. If you need some fancy schmancy logic inside the constructor to initialize the members, you're probably doing something wrong. Whatever you do, don't do:

     struct Class
     {
          Class( int foo ) : 
              m_iFoo(foo) 
          {} 
          int m_iFoo;
     };

What's the freaking point? Writing

     Class bar( myFoo );

is just as hard as writing

     Class bar;
     bar.m_iFoo = myFoo;

but it's way more bloated. Sometimes I even reduce these things to be

     int g_aFoo[MAX_FOO_COUNT];

but that's only when I get a clue. Code like Fortran! -- SunirShah

Why bother with C++ at all in that case. Just use C, and you get a smaller language that is easier to understand, no behaviour generated by the compiler that is not in the source code, and much finer control over memory allocation and code generation. -- NatPryce

C++ has way more power. You can do way more things in C++ than you can in C, which means you can make a) more efficient code (most important), b) simpler code. Hell, even the namespaces alone are worth it. See CppHeresy for Peter's answer to that same question I asked him ever so long ago. Consider that I work with XML; writing a document object model in C is ridiculous. -- SunirShah

All the TuringComplete programming languages have (by definition) exactly the same computing power. Therefore, C has as much power as C++. The only difference is how you write things. In C++ you usually use an ObjectOriented paradigm, and in C you usually use an imperative paradigm. Using classes won't create more efficient code, on the contrary: there will be vtables all over the program, instead of plain old function addresses. The only benefit of using C++ is that your code is more clear. Namespaces, classes, operator overloading and private varibles are a (syntactic) feature that makes your program much more read readable. In fact, C++ was in the beggining just a smart macro processor for C (Cfront). Writing programs in C is just as easy as writing them in C++, if you are used to it. The code can be readable, too (just translate object->method(params) to method(object,params)). -- AmirLivne

''Woah, where's the 'vtables all over the program' without the virtual??? C++ still provides tons of benefits without the virtual flavor of polymorphism.

People always think that TuringCompleteness is the only power measurement. Is assembler as powerful as LISP? No. Is COBOL as powerful as SELF. No. The fallacy here is blatant. Think harder. How else could we measure power? ... maybe parsimony? efficiency? ... When we compare programming languages' power, first we compare their mathematical strength and then we compare their utility. -- SunirShah


Moved here from MethodsShouldBePublic

Recently, I've been coding for small devices. After a year of application/system-level Java, it took awhile to relearn how to not write bloated OO junk. Noting that protection in C++ is on the same level of interest as const (i.e. for the programmer's interest, not the program's), I've come to agree with what PeterMerel said somewhere else: UseStructsNotClasses. Not only is it easier to just not use protection, but it gives you much more opportunities for optimization. Why have a constructor when you can set the members yourself? Or why not manage that Vector's internal representation directly yourself? This practice generally pisses off Java programmers, if they can wrap their head around the fact that structs and unions ARE classes. In fact, it's a good way to discover the Java programmers masquerading as embedded systems programmers. I find that an entertaining side benefit that brightens up an otherwise cynical day. ;) -- SunirShah

What exactly are you saving by not having a constructor? Nothing. You are making everyone who uses your code know what needs to be initialized and any change to the implementation will break all existing code. And why would you directly manipulate internal data structures when addItem() is perfectly understandable, has no overhead, and allows the implementation to change without breaking existing code. There's no bloat. In C++ struct/class are the same so there's nothing to worry about. In java every object has a semaphore which can be expensive because often an object should make used of a container level lock and doesn't need its own lock. --AnonymousDonor

Most Java VM implemenations will use a pool of locks instead of allocating a dedicated lock for each object that is instantiated as you describe it. A lock is reserved from the pool whenever there's a synchronization operation on an object. Locks are returned to the pool immediately after the synchronized operation. So the expense you're talking about isn't really there. -- IvesAerts

Anything other than the default constructors wastes additional bytes and cycles. Calling functions and methods cost bytes and cycles and possibly cache misses. These things are more important on small devices when doing embedded systems programming than maintainability. Helpfully, the code doesn't exceed 10 000 lines, most of which was painstakingly created by a hand-tuned CodeGenerator, and we have a massive number of UnitTests to save us.

Say you're decoding a PCDATA blob in an XML stream. Would you call addItem() on each character you uncook? How about on each CDATA substring? No. It's a waste of a function call and a comparison, and then another function call. Instead, manage the vector yourself inside the PCDATa parser so you can directly assign *pDestination++ = decodedCharacter where pDestination is the current byte inside the m_pVector and grow the vector appropriately. This is even better when parsing things like XML lists, because you precount the list to determine how large the vector is and then allocate the vector to be exactly the size you need. Not only does this save memory, but it saves a lot of time. malloc()s are slow. You don't want to grow the vector constantly, and while geometric growth of a vector by a factor of 2 is the most efficient normally, when you have a lot of such lists, you can waste a lot of memory very quickly (half the memory in the worst case).

What tools are needed here besides mathematics? Vectors are the number one data structure where public members help. But if you want tools, consider that

 struct Bar
 { 
Bar(int x);
int m_x;
 };

Bar::Bar(int x) { m_x = x; }

... Bar bar(7);

is 46 bytes whereas

 struct Bar
 { 
Bar();
int m_x;
 };

Bar::Bar() { }

... Bar bar; bar.m_x = 7;

is 38 bytes, a difference of eight bytes on CodeWarrior 8 for Palm OS on the default stationary release target (ok, not necessary most optimal, but you get the point). On VisualStudio, you save eleven bytes when compiling for minimal size (one byte for maximum speed). Sure, this is extreme, but I don't do this in my Java applications. Well, not most of them. -- SunirShah

Inlining methods will remove all method invocation overhead so you can be as efficient yet as modular as you need to be. If you are very careful about which methods are virtual your code can be as efficient has you could hack by hand. addItem() should be inlined. Perhaps bar should be instantiated once in some scope and reused. Putting it on the stack all the time is probably unecessary. What compiler packing options have you used? If size is important then declaring separate variables in a structure can be expensive. You can probably reduce memory overhead by managing your own memory instead of using the standard memory library. If new is a problem then override allocation and use object pools. There are lots of things to do before you make every client replicate the same code all over the place instead of centralizing it in the object. --AnonymousDonor

I think you misunderstand why addItem() is slow, or why inlining addItem() doesn't accomplish anything (inlining would more likely make things worse, actually). When manipulating the Vector directly, the gain doesn't come from manually inlining addItem(), but from using the Vector in ways not normally defined by the interface. There is no gain in centralizing the code in Vector as each usage is unique and/or already centralized outside Vector somewhere more appropriate. Writing your own memory pool is hardly more maintainable, and it's not necessarily more efficient than using memory more efficiently in the first place. Of course, we do have our own memory pools, each optimized for a particular task.

Declaring variables in a structure isn't less efficient than declaring them directly unless you are forcing the struct to pack at one-byte boundaries. That's pathological and pointless because most platforms have trouble fetching odd addresses.

It's obviously pointless to argue about how to optimize Bar. That was only an example to demonstrate why the extra pushes to the constructor cost bytes. This will be the same in every language on every platform. The surrounding code--or even syntax--that demonstrates how to call the constructor is irrelevant.

Finally, while there are lots of other things to do, there's also this thing to do. So, it is done. You could push it further, or you could trust that we continue to try everything and (most importantly) measure everything possible. Third rule of optimization (RulesOfOptimization), y'know. -- SunirShah


I'm missing something. I understand that in CeePlusPlus there is no difference between structs and classes, save the default access level: classes default to private, structs to public.

I default to public (MethodsShouldBePublic) so I end up declaring everything as structs. This saves some characters and eliminates noise. For example:

 class foo : public bar {};
becomes

 struct foo : bar {};

However, a collegue has pointed out that this practice confuses the hell out of lesser mortals. She suggests that if I do not want to earn the scourge and fury of the maintainer, I should just suck it up, use classes over structs, and type the extra public. Bah!


See CppRefactorStructToClass, ObjectsAreDictionaries


CategoryCpp


EditText of this page (last edited August 15, 2014) or FindPage with title or text search