Register    Login    Forum    Search    FAQ

Board index » Everything




Post new topic Reply to topic  [ 14 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: class vs struct mindset
 Post Posted: October 16th, 2017, 7:01 pm 
 

Joined: February 28th, 2013, 3:23 am
Posts: 2828
Location: Oklahoma, United States
When I first started learning C++ I had no idea if there was a difference between class and struct. I spent a few hours searching google and reading stackoverload posts and came to the understanding that with the exception of access they are the same thing. Class members ( variables ) and methods ( functions ) are always private unless explicitly made public using public: before a section of members and methods while struct members and methods are always public by default.

Another thing I started to wonder is why have these two things that are so similar in the language and after a bit more looking around I noticed a pattern of usage for the two. Classes always seemed to be used when there were functions involved while structs were mostly used for POD ( plain ole data ). This seemed logical to me for some time and still to this day. When I say plain ole data, I'm thinking of just a grouping of variables that doesn't have behavior and only controls the behavior of another object. For instance, instead of having a function with a lot of parameters, you'd fill in a struct and pass the struct. This is pretty common practice in the DirectX and Windows APIs. Not to mention the phrase 'data structure' does not make me think of a class, but it does make me think of a struct.

I kind of believe that this thinking is also a throwback to C where struct was only able to have data, functions were not allowed. Now I didn't start with C technically. Chili did start by teaching C in his original series as a build up to C++, but he did now really push any C coding idioms. With C++, since structs don't have the same restrictions with struct and they do allow member functions, I see no reason not to add constructors and other functions that might be useful.

For those that have gotten to experiment with inheritance, you know that when you inherit a class you must specify what type of inheritance you want; public, protected or private. Most of the time you end up writing public: but with structs since they are public by default, you can omit the type of inheritance.
Code:
class base {};
class derived : public base{};

vs

struct base{};
struct derived : base{};


Another thing that I've been considering for a while now is how to structure my class layout. Since they are private by default, do I put the private data at the top to avoid having to type private before the private members or methods?

Code:
class MyClass
{
   int age = 42;
   std::string first, last;

public:
   int GetAge()const;
   std::string GetFirstName()const;
   std::string GetLastName()const;
   std::string GetFullName()const;
};

I've seen this approach quite a bit and it is logical. Class members and methods are private by default, so put the private stuff first. I do have a problem with this though. If I want to use your class in my own code and if your class is quite lengthy I have to hunt down the public interface functions which are all the way at the bottom. I don't need to know about the functions and data I don't have access to, so why list them first?

Currently, I tend to put the public stuff first so anyone looking to see externally callable functions will be able to find them more easily, including myself. This does require then I now put public: before the public interface and explicitly mark which functions and data members I want to be private. With the mindset of wanting to put default access members and/or methods at the top and the mindset of wanting to put the public interface near the top, it stands to reason for most cases one should prefer using struct instead of class as their go to data structure.

Code:
struct MyClass
{
   int GetAge()const;
   std::string GetFirstName()const;
   std::string GetLastName()const;
   std::string GetFullName()const;

private:
   int age = 42;
   std::string first, last;
};

Public interface is at the top. Struct is public by default so I don't have to specify public at the beginning every time, and now I only have to explicitly specify what I want private. I think this is probably what I'm going to start doing from now on. I will have to get over my mindset of struct being reserved for strictly data containers with no behavior with classes have behavior and state. My new mindset will have to be, which makes code more reasonable.

There is a coding idiom of creating interfaces, which deal strictly with inheritance. An interface in this instance is a group of function signatures without definitions and cannot have data members.
Code:
// This would be considered an interface.  Only function signatures, no data members allowed.
struct IPerson
{
   virtual int GetAge()const = 0;
   virtual std::string GetFirstName()const = 0;
   virtual std::string GetLastName()const = 0;
   virtual std::string GetFullName()const = 0;
};

// This would be an implementation of the IPerson interface. 
struct Person : IPerson
{
   int GetAge()const override;
   std::string GetFirstName()const override;
   std::string GetLastName()const override;
   std::string GetFullName()const override;

private:
   std::string ConcatName()const;

// Private not needed here because everything below ConcatName would be private.
// Only used as a separator between private methods and private members
private:
   int age = 42;
   std::string first, last;
};


The name "interface" means something you interact with so there is no need for private or protected methods. For that reason, a struct is a much nicer fit than class. The concrete struct of Person can have private methods, maybe a helper function that concatenates the first and last name and returns it to GetFullName for instance.

Anyway, just thought I'd try invoking a discussion and make a logical argument for defaulting to struct over class and challenge mindsets of experienced programmers here on the forum.

_________________
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com


Top 
 Profile  
Reply with quote  
 Post Posted: October 17th, 2017, 4:09 am 
 

Joined: November 30th, 2013, 7:40 pm
Posts: 599
Location: Merville, British Columbia Canada
I use structs a lot these days, especially for data packets that i want to initialize something with or use as a return set of data. I've rarely used one as a base struct or interface. I find myself creating a default set of initializing data using structs.
For instance say i want to initialize an object with core attributes but are different for each object i'd do something like this :
Code:
class ObjectDataSets
{
public:
   struct objData
   {
      int hitPoints;
      int strength;
      objData(int hp, int s)
         :hitPoints(hp), strength(s)
      {}
      
   };
   static objData GruntData()
   {
      return objData(10, 10);
   }
};

quite often i don't care about private or protected data so that's when i use a struct. A little off your topic but that's my dance with structs ;)

_________________
Curiosity killed the cat, satisfaction brought him back


Top 
 Profile  
Reply with quote  
 Post Posted: October 17th, 2017, 4:42 am 
 

Joined: February 28th, 2013, 3:23 am
Posts: 2828
Location: Oklahoma, United States
Yeah, I had mentioned using structs for init data in a way as that is how the DX and Win32 APIs handle it. For instance, how creating a 2D texture for d3d11 you need to fill out a D3D11_TEXTURE2D_DESC struct and pass it to ID3D11Device::CreateTexture2D.

I've thought about doing that for things that don't have different behavior, just different starting data. One class, different structs representing the different entities' attributes.

_________________
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com


Top 
 Profile  
Reply with quote  
 Post Posted: October 17th, 2017, 9:36 am 
Site Admin
User avatar

Joined: December 31st, 2011, 4:53 pm
Posts: 3470
Location: Japan
For the longest time I've mostly felt like it is a matter of preference to a large degree, as long as you choose a logical convention and are consistent with it.

Nowadays I cannot really recommend anything that violates the core guidelines. It would be considered bad form to 'do your own thing' in this case. Anything with a private member or even just an invariant (implied) should be a class, for starters.

Also, for plain structs like what Godin shows there, as per the guidelines a struct is fine, but I wouldn't even bother making ctor when you have aggregate init. Just more code to maintain.

_________________
Chili


Top 
 Profile  
Reply with quote  
 Post Posted: October 17th, 2017, 1:55 pm 
 

Joined: February 28th, 2013, 3:23 am
Posts: 2828
Location: Oklahoma, United States
chili wrote:
Nowadays I cannot really recommend anything that violates the core guidelines. It would be considered bad form to 'do your own thing' in this case. Anything with a private member or even just an invariant (implied) should be a class, for starters.

I still feel this is just a matter of mindset. The way people expect things to be because that's the way they've always done it. I tried a few google searches to find a definitive answer, but so far I get opinions based on use struct for POD so that your library/interfaces would be compatible with a C compiler. This of course is a ridiculous notion as C doesn't have classes, namespaces, templates or function overloading, so your C++ code most likely won't compile in a C compiler anyway unless you are aiming for that and don't use any C++ features.

chili wrote:
Also, for plain structs like what Godin shows there, as per the guidelines a struct is fine, but I wouldn't even bother making ctor when you have aggregate init. Just more code to maintain.


I believe aggregate initialization was, partly implemented for classes that have multiple constructors so you didn't have to use the initializer list of each ctor, a way of reducing code duplication.

_________________
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com


Top 
 Profile  
Reply with quote  
 Post Posted: October 18th, 2017, 12:57 am 
 

Joined: November 30th, 2013, 7:40 pm
Posts: 599
Location: Merville, British Columbia Canada
chili wrote:
Also, for plain structs like what Godin shows there, as per the guidelines a struct is fine, but I wouldn't even bother making ctor when you have aggregate init. Just more code to maintain.

Code:
class ObjectCreator
{
public:
   struct objData
   {
      int hitPoints;
      int strength;
   };
   static objData GruntData()
   {
      return{ 10,10 };
   }
};

Nice, thanks chili never thought of doing it like this.

_________________
Curiosity killed the cat, satisfaction brought him back


Top 
 Profile  
Reply with quote  
 Post Posted: October 18th, 2017, 1:18 am 
Site Admin
User avatar

Joined: December 31st, 2011, 4:53 pm
Posts: 3470
Location: Japan
albinopapa wrote:
chili wrote:
Nowadays I cannot really recommend anything that violates the core guidelines. It would be considered bad form to 'do your own thing' in this case. Anything with a private member or even just an invariant (implied) should be a class, for starters.

I still feel this is just a matter of mindset. The way people expect things to be because that's the way they've always done it. I tried a few google searches to find a definitive answer, but so far I get opinions based on use struct for POD so that your library/interfaces would be compatible with a C compiler. This of course is a ridiculous notion as C doesn't have classes, namespaces, templates or function overloading, so your C++ code most likely won't compile in a C compiler anyway unless you are aiming for that and don't use any C++ features.


I recommend that everyone, once they reach a certain level of capability, read the Core Guidelines. They are written and maintained by Bjarne and Herb, and they provide lucid, rational norms that you should be aiming for if you're interested in developing clean, readable, maintainable, and scalable code. This is the industry standard for modern C++ development.

I'll link to section describing the concept of class and when it should be applied (as opposed to struct). See C.1~C.9:

http://isocpp.github.io/CppCoreGuidelin ... es#S-class

This guide is great in that, you can click on any of the points there to see a more detailed rationale, often with a concrete example.

Also, in regards to things like being able omit the public specifier in inheritance using structs, I found this amusing because I was just going over the top recommended C++ books looking for ideas for teaching inheritance, and the number one text "C++ Primer" goes out of its way to note that you should not be doing this. :D

Good thread papa, I like the discussion.

_________________
Chili


Top 
 Profile  
Reply with quote  
 Post Posted: October 18th, 2017, 8:19 am 
 

Joined: February 28th, 2013, 3:23 am
Posts: 2828
Location: Oklahoma, United States
In the core guidelines, they use this example:
Code:
struct Distance {
public:
    // ...
    double meters() const { return magnitude*unit; }
    void set_unit(double u)
    {
            // ... check that u is a factor of 10 ...
            // ... change magnitude appropriately ...
            unit = u;
    }
    // ...
private:
    double magnitude;
    double unit;    // 1 is meters, 1000 is kilometers, 0.0001 is millimeters, etc.
};

When talking about encapsulation and order of accessors; public stuff, protected stuff, then private stuff. This is even after the discussion of using class over struct if there are any private members.

It still feels as though it's more of a mindset for older programmers' reasoning. Structs have public access by default so it is not expected to have private members. A class however has private access by default, but for some reason it is also expected to have a public interface as well.

The thing to take away from this I suppose is if you are going to work for someone or with other experienced programmers, you need to consider how they will reason about your code. If everyone in your organization is use to structs for data and classes for objects with behavior and state, then it stands to reason your code is going to be out of place if you choose to implement struct as if it were a class.

_________________
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com


Top 
 Profile  
Reply with quote  
 Post Posted: October 19th, 2017, 10:10 am 
Site Admin
User avatar

Joined: December 31st, 2011, 4:53 pm
Posts: 3470
Location: Japan
The prescribed order seems logical to me. You put the interface first because it's the most important part, and it is the part you interact with and what you are interested in. Keeping types without invariants all public also makes sense (you could make private just to insulate implementation from interface, but that is a weaker motivation). And if you're making all public, struct seems like a good fit, even disregarding the history of the language and its origins. That leaves class to deal with actual classes (data types that have a class invariant).

Your point about the example that uses struct with private members is a bit of a nitpick. It is clear to me that this is just an oversight of editing here. In fact, I submitted a pull request with an amendment that addresses this issue, and it looks like it will get the green light.

https://github.com/isocpp/CppCoreGuidelines/pull/1061

_________________
Chili


Top 
 Profile  
Reply with quote  
 Post Posted: October 19th, 2017, 5:11 pm 
 

Joined: February 28th, 2013, 3:23 am
Posts: 2828
Location: Oklahoma, United States
Ok, I'm a bit confused about the word 'invariant'. It seems to me that invariant is a "rule" for an object enforced by assertions and such during public interface interactions.

Class Invariant

So using the constructor of a class would throw if valid values aren't given and same for any Set functions for instance. That is what I get from the description. This really doesn't seem like a strong argument for using class over struct, if as far as the language is concerned struct is same as class.

In the examples, a class Date is used and as a rule a day can only be 24 hours long. The invariant is the adherence to this rule through assertions or if conditions during creation (ctor) and setters. Again, since the C++ language allows us to denote private access in structs, then there is little argument not to use them for simplicity over classes.

It took me a few reads to kind of understand what you are getting at: "...And if you're making all public, struct seems like a good fit, even disregarding the history of the language and its origins. That leaves class to deal with actual classes (data types that have a class invariant)."

Here's what I hear when I read this: "Since struct is public by default, there is no need to have these rules ( invariants ) and since classes are private by default it enforces these rules." Let me know if I'm on the right track.

So aside from invariants, do you believe structs should not have methods? If you were defining an abstract class with no private members nor methods, would you still use a class with public access?
Code:
struct IMyClass
{
     virtual void SetState( int StateValue ) = 0;
};
vs
class IMyClass
{
public:
     virtual void SetState( int StateValue ) = 0;
};

I agree that there is no point in having state changing functions if all the data is public, just use the data members directly.

I think there is something that I have overlooked and it's something that may be in the guidelines, but it's something I've heard Bjarne talk about in CppCon videos. He always talks about what to prefer and one of them I think is prefer private over public without stating directly prefer class over struct. Encapsulation is encouraged for safe programming and class forces the programmer to explicitly declare something he/she wants to allow access to by use of public. On the other hand, struct being public by default forces the programmer to be explicit in what is private. This may be closer to what you are trying to get at in your last post.

_________________
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com


Top 
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
 
Post new topic Reply to topic  [ 14 posts ]  Go to page 1, 2  Next

Board index » Everything


 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for: