Why Choose Struct Over Class

Why Choose Struct Over Class?

According to the very popular WWDC 2015 talk Protocol Oriented Programming in Swift (video, transcript), Swift provides a number of features that make structs better than classes in many circumstances.

Structs are preferable if they are relatively small and copiable because copying is way safer than having multiple references to the same instance as happens with classes. This is especially important when passing around a variable to many classes and/or in a multithreaded environment. If you can always send a copy of your variable to other places, you never have to worry about that other place changing the value of your variable underneath you.

With Structs, there is much less need to worry about memory leaks or multiple threads racing to access/modify a single instance of a variable. (For the more technically minded, the exception to that is when capturing a struct inside a closure because then it is actually capturing a reference to the instance unless you explicitly mark it to be copied).

Classes can also become bloated because a class can only inherit from a single superclass. That encourages us to create huge superclasses that encompass many different abilities that are only loosely related. Using protocols, especially with protocol extensions where you can provide implementations to protocols, allows you to eliminate the need for classes to achieve this sort of behavior.

The talk lays out these scenarios where classes are preferred:

  • Copying or comparing instances doesn't make sense (e.g., Window)
  • Instance lifetime is tied to external effects (e.g., TemporaryFile)
  • Instances are just "sinks"--write-only conduits to external state (e.g.CGContext)

It implies that structs should be the default and classes should be a fallback.

On the other hand, The Swift Programming Language documentation is somewhat contradictory:

Structure instances are always passed by value, and class
instances are always passed by reference. This means that they are
suited to different kinds of tasks. As you consider the data
constructs and functionality that you need for a project, decide
whether each data construct should be defined as a class or as a
structure.

As a general guideline, consider creating a structure when one or more
of these conditions apply:

  • The structure’s primary purpose is to encapsulate a few relatively simple data values.
  • It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an
    instance of that structure.
  • Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced.
  • The structure does not need to inherit properties or behavior from another existing type.

Examples of good candidates for structures include:

  • The size of a geometric shape, perhaps encapsulating a width property and a height property, both of type Double.
  • A way to refer to ranges within a series, perhaps encapsulating a start property and a length property, both of type Int.
  • A point in a 3D coordinate system, perhaps encapsulating x, y and z properties, each of type Double.

In all other cases, define a class, and create instances of that class
to be managed and passed by reference. In practice, this means that
most custom data constructs should be classes, not structures.

Here it is claiming that we should default to using classes and use structures only in specific circumstances. Ultimately, you need to understand the real world implication of value types vs. reference types and then you can make an informed decision about when to use structs or classes. Also, keep in mind that these concepts are always evolving and The Swift Programming Language documentation was written before the Protocol Oriented Programming talk was given.

When should you use a class vs a struct in C++?

The differences between a class and a struct in C++ is:

  • struct members and base classes/structs are public by default.
  • class members and base classes/struts are private by default.

Both classes and structs can have a mixture of public, protected and private members, can use inheritance and can have member functions.

I would recommend you:

  • use struct for plain-old-data structures without any class-like features;
  • use class when you make use of features such as private or protected members, non-default constructors and operators, etc.

When should I use a struct instead of a class?

MSDN has the answer:
Choosing Between Classes and Structures.

Basically, that page gives you a 4-item checklist and says to use a class unless your type meets all of the criteria.

Do not define a structure unless the
type has all of the following
characteristics:

  • It logically represents a single value, similar to primitive types
    (integer, double, and so on).
  • It has an instance size smaller than 16 bytes.
  • It is immutable.
  • It will not have to be boxed frequently.

When to use struct over class in c++

The only difference between the two is that by default struct are public while class members are private.

My rule is to use struct when I mean a clump of related data without any special semantics on reading/writing them. Use class when I intend to wrap the data in richer semantics (read methods) that are more meaningful, provide any needed protection, or implement something more abstract.

How should I reason when I have to choose between a class, struct and enum in Swift?

ChristopheD's and Jack Wu's answers are good, but I feel they don't touch on enums, or miss their importance. Swift enums are (meant to be) a full implementation of algebraic data types. Classes and structs are traditionally used to model data in object-oriented languages, but enums are usually limited to being used as a convenient way to limit the value of a variable to a limited number of possibilities. E.g. (C++):

enum MaritalStatus { Unmarried, Married, Divorced, WidowedOrWidowered };
MaritalStatus m = Unmarried;

Swift enums can do the above but they can do a lot more. Of course the Language Guide has a pretty good barcode modelling example but the best example I know of that really drives home the point of modelling data with algebraic data types is Scott Wlaschin's presentation: http://www.slideshare.net/ScottWlaschin/ddd-with-fsharptypesystemlondonndc2013

You would probably benefit from going through the whole presentation but really to 'get' the point all you need to see is slide 60 where he shows how to model a 'payment method' in a typical line-of-business app.

The examples in the presentation are in F# but F# isn't that far off from Swift and you can pretty easily map between them. E.g., the payment methods enum in Swift would look like:

enum PaymentMethod {
case cash // No extra data needed.
case cheque(Int) // Cheque #.
case card(CardType, CardNumber) // 2 pieces of extra data.
}

The point of the above is that each order's payment method can be only one of the above three methods. Anything else will not be allowed by the compiler. This is a very succinct alternative to building entire class hierarchies to model these almost trivial things.

The presentation really takes off from there and the best part is Swift can do almost everything that F# can in terms of data modelling, using optional types, etc.

Why do we need a `class` in C++, when a `struct` can be used to achieve the same?

You don't need classes, the language just gives you another option to choose from. Technically, you're right, you can achieve anything a class can do with a struct.

Besides the default access level, there's also the meaning most programmers associate with the two - struct generally means a light-weight, typically POD, data-type with little to no functionality. A class is usually associated with something bigger.

c# struct vs Class performace, Design focus

The msdn recommendations (listed in another answer) offers some guidance. For performance you should consider their usage and where the difference between structs and classes matter. The most important thing is to only use structs when the type is an actual value type, i.e. has value semantics. Using a class for a value type, or a struct for something that should be a reference type will quickly give confusing results with unintened copying or unintended references. Also remember that structs should always be immutable.

Only in extremely performance-sensitive situations should you ignore any of these basic rules (Case in point: The framework breaks the rules for the List.Enumerator structure!).

Perf considerations for structs vs classes:

If you pass your struct as an argument to a method, you will create a copy of the struct each time. This is why the docs recommend not making structs larger than 16 bytes.

double Distance(Vector3D a, Vector3D b)   // Copies both 24-byte structs
{
return Math.Sqrt((a.X - b.X *....);
}

In the scenario above, a 3D vector of 3 doubles would make 24 bytes and would be larger than the recommended 16, but I'd still argue that a struct makes sense since it is clearly a value type, especially you have a Vector2D containing two doubles (16 bytes) that is a struct!

The key to using structs efficiently, and where their performance really shines, is to use them for cache locality and avoiding many allocations. To re-use the Vector example above, if you have this method

double AverageDistanceFromOrigin(Vector3D[] points) // Single reference passed
{
double sum = 0.0;
for(...)
sum += Math.Sqrt(points[i].X... + ... + ...) // No copies
return sum/points.Length;
}

You may see a good performance difference in favor of structs. The reason is that now you are passing a single reference (the array) to the method, so there is no extra overhead for copying the struct per call(Note that the method does not call a distance-method for each entry in the array).

The array-of-structs is also laid out consecutively in memory like [x1, y1, z1, x2, y2, ...] so the cpu will load e.g. 16 coordinates at a time into the cache , leading to few cache misses. Compare that to a class Vector3D implementation: now an array of vectors will be allocated and each entry in the array will also be a reference that has to be heap allocated, and later garbage collected. The array is now an array of references [ref to v1, ref to v2, ref to v3] each of which may have any address in the heap, and may not sit next to eachother. This can lead to many more cache misses than the struct case.

  • Use structs only if your type has value semantics (and vice versa).
  • Don't pass large structs individually to methods; Operate on lists/arrays of structs to avoid copying
  • Don't consider the 16-byte limit as a hard limit: you can use much larger structs, but remember to avoid passing them individually to methods.


Related Topics



Leave a reply



Submit