The Google C++ style guide advises using structs for passive objects as opposed to classes. What are some good use cases (with examples) of passive objects ? What are the advantages of using structs (as opposed to classes) for passive objects ?
Passive Objects in C++
2.8k views Asked by ravi tandon AtThere are 2 answers
What are some good use cases (with examples) of passive objects?
With a fully Object Oriented, encapsulated user-defined type developers may reasonably expect to be able to freely add, remove, reorder or change the type of private data as long as the client-facing API/functionality is honoured.
Sometimes you don't want such encapsulation, and need to emphasise to developers that that freedom doesn't exist and that instead "clients" at some level - whether elsewhere in the same class (for nested classes/structs) or namespace, implementation file, library, application or IPC-connected "system" are deliberately allowed to couple themselves to the chosen data members.
That might be done because:
You want a specific data representation that matches needed encoding when reading or writing memory or streams because there's an agreement with the client(s) about that data members or layout (e.g. the ping protocol, Tokyo Stock Exchange Arrowhead Standard protocol, MS Excel version 7.0 document standard, an assembly language routine you'll call that expects a pointer to specific data, some hardware bus accessed via "memory" read/writes) that's "bigger" (more stable/longer lived, official) than any single data-processing implementation: the possibility of a data change is so extremely unlikely (or impossible - you can't retrospectively change the data format used by an Excel version from yesteryear) and/or fundamental that you'd be prepared to have your program break and have to revisit all uses of the data to fix it.
The scope of "client" use is relatively small (e.g. a private class/struct nested in another, or one in an anonymous namespace in an implementation file), such that there's limited rework to be done if the data changes, and there's a net code writing/maintenance/understandability benefit from the simplicity and concision of public data members, and avoiding access specifiers and trivial get/set functions.
An example: a Weighted_Average
class that internally kept a list of however-many samples with timestamps for when they were captured - it might reasonably lump an instance of the data type along with say a timeval
or std::chrono::time_point
into a struct
it can use to instantiate a vector
or list
.
What are the advantages of using structs (as opposed to classes) for passive objects ?
There are minimal functional advantages (the only language difference is in the default access to bases and members, which makes struct
more concise if you want such exposed).
The motivation for Google's style guide is clearly expressed therein:
We add our own semantic meanings to each keyword
That's simply saying that they're using struct
vs class
to encode whether a type is passive (by their definition) or not.
More generally, many coding styles use the choice of class
vs struct
to encode some vaguely similar aspect of the encapsulation level of the type. Personally, there are lots of cases I'd use struct
that aren't explicitly permitted by the Google guide, such as:
deriving a concrete observer from an abstract observer while overriding virtual callbacks for various events, where the observer was private to the class it was used in anyway
writing simple stateless functors (e.g.
Hash
,Equal
for instantiatingstd::unordered_map
) or predicatesprivate
struct Impl;
for the pImpl Idiom
Further, I decide based on the dependencies/coupling I'm prepared to let clients have, which is unrelated to whether the struct
will have more complex member functions. I see no sound reason for Google's restriction on struct
member functions where those functions aren't making pretentions of enforcing post-conditions or invariants that are actually unenforcable given the direct access available to data members. In other words - as long as the struct
's data's good after the function call, who cares whether the function did. As a concrete example, if struct timeval { int64_t tv_sec, tv_usec; };
were given a normalise()
function that ensured tv_usec
was in the [0..1,000,000) microsecond range and sometimes adjusted tv_sec
accordingly, well and good: it doesn't interfere with any other use of the data.
A passive object is usually an object that won't be interacting with other objects by itself. An example of this would be the mathematical representation for a point:
A point won't technically interact with other objects, it is passive.
A non-passive object would be an object that would use other objects to perform tasks, especially with functions. Writing a class that would handle networking I/O should be a class, as it will use many, many other objects to handle sockets, addresses, ect. A quick example from a current program I have:
As far as benefits, having a struct means you won't need to have gettors/settors for your members. Struct members/methods are implicitly
public
unless explicitly defined so. This means less code, as well as a minor memory improvement. If an object has no functions, a function table will not be created. Not that big, but still an improvement. You also don't have the overhead of function calls.Classes are the opposite. They are implicitly
private
, and will only be public if explicitly defined as such. Private data means you need functions to interact with said data. This usually means hiding it behind an interface. Here's my interface for an IRC client I'm currently making:This class is not passive at all! I use structs such as
sockaddr_in
. I also use some classes likestd::string
. Making this is class is good, because I hide the implementation for the user to keep life simple.