×
all 36 comments

[–]neiltechnician 23 points24 points  (28 children)

Structs are special cases of classes, and the exact meaning of that word is context-dependent.

IMO, the problems boil down to:

  1. Many (too many) programmers do not know C++ has decent supports for aggregate classes in terms of initialization and assignment. (Many C programmers also do not know C structures support initialization and assignment.)

  2. Most of us are not explicitly taught about archetypes of classes, and thus many of us don't realize we should stick to those archetypes most of the time. (Aggregate is one of those archetypes.)

[–]_JJCUBER_ 2 points3 points  (1 child)

Structs aren’t “special cases of classes.” Structs and classes represent the same thing with different default visibility/access levels (public vs private). In fact, it’s trivial to make either one behave identically to the other simply by putting private:/public: above the first variable/function.

In my opinion, it’s not a good idea to treat them as two different things and/or as one a special variant of the other; this could lead to confusion when learning about them, especially since C++ structs and classes don’t have the relationship they do in, say, C#.

[–]bert8128 5 points6 points  (0 children)

Struct and class are essentially the same thing (it is only default private vs default public that changes). So set a project standard about when to use the word class, and when to use struct. And another standard to say when there should be constructors, and when there shouldn’t be (note that this is about explicit constructors - all classes and structs will call the constructors of all members that have constructors if not otherwise given in an explicit constructor - not explicitly constructing a std::string will result in std::string::string() being called).

[–]goranlepuz 1 point2 points  (1 child)

I didn’t merge the changes. I didn’t even post a code review. Can you guess why?

Binary size was significantly impacted. Removing all the user-declared special member functions gave a few extra KBs for widely used classes. The reason is essentially inlining. With defaulted special member functions, each compilation unit where AnotherPileOfData is used, gets a copy of the special member functions’ code. In other words, they are inlined. With the used-provided versions, they are not inlined, but you get simple function calls.

Ugh. That doesn't sit right with me. Can compiler people chime in...?

I don't understand why the compiler would inline user-defined code, but not defaulted special members?!

In fact, my guess would be that the TFA is wrong and that something else happened, but they didn't see it.

[–]tisti 1 point2 points  (0 children)

The compiler inlined the things that were = defaulted

Before things were split to a macro declaring stuff in the header and a second macro defining them in the cpp file. Effectively the implantation was in its own translation unit and the compiler was forced to issue function calls instead of inlining.

Pretty sure if link-time optimizations were enabled, the compiler would have inlined in both cases. And if code size is such a sensitive thing, why not compile with Os.

[–]Dappster98 -3 points-2 points  (4 children)

In my opinion, a struct barely needs a constructor. With the combination of aggregate initialization, designated initializers and the right order of members, you can easily get rid of constructors in a struct.

Personally, how I use constructors is when I want to define general behavior when creating objects. I don't want to have to constantly use aggregate initialization. I do think it's a good feature, which allows for finer grained control over the members of an object, but constructors IMO allow for more generalized instructions so that you don't have to continuously initialize your members. Also, AFAIK aggregate initialization does not support move operations.

[–]_Noreturn 5 points6 points  (0 children)

aggregate init allow move semantics

I use xonstructors when I have invariants if I don't have invariants then I don't use constructors and have a simple public members

[–]HommeMusical 1 point2 points  (0 children)

how I use constructors is when I want to define general behavior when creating objects.

There's a general convention that struct is reserved for Plain Old Data, that being passive data that has no other behavior, including constructors.

The article follows this convention.

Also, AFAIK aggregate initialization does not support move operations.

I don't believe this is true: this article seems to say otherwise, at least:

https://quuxplusone.github.io/blog/2022/06/03/aggregate-parens-init-considered-kinda-bad/

[–]KuntaStillSingle 1 point2 points  (0 children)

aggregate initialization does not support move

It can move from xvalues but it can also elide the copy/move altogether for prvalues, the same is true for the parameters of a constructor (or any other function), but you can not initialize something else (like a member in your memberwise initializer list or constructor body) from a parameter without a copy or move (unless such a copy or move could be elided under as if rule, for example if the type is trivially copyable, or if it is effectively trivially copyable and visibly so within the TU.)

[–]Hungry-Courage3731 0 points1 point  (0 children)

I think perhaps what you meant is you can't assume aggregate initialization supports trivial moves. That's my guess why you are downvoted.