How much functionality is “acceptable” for a C++ s

2019-03-18 04:11发布

问题:

My first post so please go easy on me!

I know that there's no real difference between structs and classes in C++, but a lot of people including me use a struct or class to show intent - structs for grouping "plain old data" and classes for encapsulated data that has meaningful operations.

Now, that's fine but at what point do you start to think that something isn't just a struct anymore and should become a class?

Things I think are reasonable for structs to have:

  1. constructors with simple initialisation code only.
  2. serialization code such as stream insertion / extraction operators.

Things I'm not so sure about, but would probably do:

  1. comparison operators
  2. Simple transformation functions - for example byteswapping all the members after receiving data from an external source.

I don't think structs should have:

  1. dynamic memory allocation.
  2. destructor.
  3. complex member functions.

Where do the boundaries lie???

Also, is it reasonable to have class instances as members of a struct? e.g.

class C {private: int hiddenData; public: void DoSomething();};

struct S {int a; float b; C c; };

S s; s.c.DoSomething();

Remember, I'm not on about what you CAN do with C++, I'm interested in what you SHOULD do when designing good software.

Thoughts?

回答1:

Class vs. struct

Using class or struct keyword is a matter of taste together with the 'feeling' it produces on the reader. Technically they are equivalent, but readability is better if structs are used for PODs and C-struct types and classes for anything else.

Basic things that should go in a C++ struct: constructor that initializes the data (I dislike using memset, and it can later bite back if the POD evolves into something different) or construction from other types but not copy constructor.

If you need to define a copy constructor or assignment operator because the compiler generated is not good enough, make it a class.

It is common to use structs also for functors that will be passed to STL algorithms and template metaprogramming, as in

struct square_int {
   int operator()( int value )
   {
      return value*value;
   }
};
std::transform( v.begin(), v.end(), v.begin(), square_int() );

or

// off the top of my head
template <typename T>
struct is_pointer { enum { value = false } };

template <typename T>
struct is_pointer<T*> { enum { value = true } };

Member methods vs. free functions

Besides what I have said before, that do not add to what others already answered, I wanted to put some focus on other types of functions that you comment in your post, as comparison operators and the like.

Operators that are meant to be symmetric (comparison, arithmetic), insertion and deletion operators and transformations are usually better implemented as free functions regardless of whether you declare it as a class or struct.

Symmetric operators (with regard to data types) are not symmetric if they are implemented as member functions. The lookup rules won't cast the left hand side to call a member function, but it will apply the same cast to match a free function.

   // Example of symmetry with free functions where method would be asymmetric
   int main()
   {
      std::string( "Hello " ) + "world"; // compiles as free / member function
      "Hello " + std::string( "world" ); // compiles with free function, fails with member function definition of +
   }

In the code above, if operator+ were a member method of std::string the compiler would fail to compile as it cannot cast the const char* literal into a std::string to use the member method.

Insertion and extraction from streams must always be implemented as free functions as the stream is always the left hand side of the operation.

Keeping transformations as free functions decouple the two different types. If A and A' can be converted into one another and you decide to implement transformations as members of A, then A must know A' and all uses of A will depend on A' whether you use it or not. If you define the transformation as a free function, A is complete without A' and the coupling between the two classes/structs will be smaller. The same goes for transformations to/from network, serialization and deserialization. When you implement them inside the class/struct you are forcing all users to know about those transforms.



回答2:

I think there are three major, coherent schools of thought:

  1. People that don't care either way and use struct and class interchangeably.
  2. People that use structs only to represent small POD.
  3. People who use structs as records.

I can't make a conclusive argument for either of these strategies. I tend to follow path 2 but I also use structs for non-POD types when I see it fitting, especially for function objects (even if these may not fulfil POD requirements).

(Incidentally, the C++ FAQ lite has a pretty good definition of POD).

EDIT I didn't touch template metaprogramming techniques, such as using struct for placeholders (type tags) or to implement metafunctions. I guess there's absolutely no controversy in these cases: since they never contain methods (or even data), always use struct).



回答3:

My personal preference is to only use structs if there's no methods at all. If I need to add a method for any reason, it's a class.



回答4:

From reading through some STL source code which ships with Visual Studio, it appears that one criteria in use is are things "mostly public" (start with a struct) or "mostly private" (start with a class)?

In a similar vein, if what you write near the top (maybe because it's important) of your class is public, then go with struct. On the other hand, if you list member data first use class.



回答5:

I use classes and encapsulation when I need to maintain invariants and data integrity. If you have no invariants, and the data really is just a bucket of items, it has always been fine in our shop to use struct even if you add fancy helper constructors or functions. However the more you do decorate it, that should make you stop and think that maybe it should be a class.

If you do have to be POD compatible (for say interfacing to C code) you do still need to use struct. However you can wrap this struct in a class and expose it with a get() function for interfacing with C API's. Or create a helper function to return a proper POD struct from your class.



回答6:

You could look at what the Standard Library does. Everyone's favourite struct std::pair only has constructors.

I find the use of constructors with structs so convenient and natural that I can't imagine doing without them. I never give structs any other methods, but of course there may be free functions or members of other classes that take them as parameters.



回答7:

I might be in the minority, but I use structs to mean one thing, "the order of the bits matters". Anything that must be serialized to disk or network, or has to be compatible with some third party library and needs to be in the right order, Or if it's doing some kind of bit field magic as a processor specific optimization, that always goes into a struct. Rearranging the fields in a struct, therefore, always has some kind of consequences and should be carefully thought out.

classes, however, have fields that are only semantically meaningful. If for some reason I or someone else wants to rearrange or modify the data members of a class, this can happen pretty freely.



回答8:

I'll add a very few methods to a struct, as a matter of convenience, only if I'm actually using them. Of course all of them are public. If I need any more than this, it immediately gets converted to a class.

  1. A default constructor, to initialize the data members to known values.
  2. A constructor with one parameter for each member.
  3. operator<, for easy inclusion in sets and maps.
  4. operator==.


回答9:

Consistency is most important. The point of conventions are to give a common point of reference for all those reading your code in the future.

Personally, I avoid structs if I feel I need the functionality of a class.
AKA: "plain old data" approach.

Look at your project and set a standard, or adhere to the one already there.

Though, I suggest you try to avoid inheritence, especially if all the struct is doing is holding POD. Nothing worse then tracing a bunch of super classes for an integer or char.



回答10:

alot of people including me use a struct or class to show intent - structs for grouping "plain old data" and classes for encapsulated data that has meaningful operations.

IMHO, this differentiation is a misunderstanding but it's well understood why it is used like that. It is based on traditional conventions and built-in feeling about class vs struct. I'd follow Marshall Cline's suggestion:

7.8 What's the difference between the keywords struct and class?

Since that's the connotation most people already have, you should probably use the struct keyword if you have a class that has very few methods and has public data (such things do exist in well designed systems!), but otherwise you should probably use the class keyword.

Personally, I (over)use struct keyword for metafunctions



回答11:

Another common use of structs is for template stuff and local RAII or functor classes. These are one-off bits of code that are closer to functions than they are to whole classes, and requiring the extra public: declaration is just silly. If you look at boost, you'll see a lot of stuff like this:

template<
      bool C
    , typename T1
    , typename T2
    >
struct if_c
{
    typedef T1 type;
};

It's clearly not a POD (in fact it doesn't hold any data at all). In other cases you can have RAII class that consist of only a constructor/desctructor:

struct LogOnLeavingScope : public NonCopyable
{
    string Message;
    LogOnLeavingScope(const string &message) : Message(message) {}
    ~LogOnLeavingScope() { Log(Message); }
];

Functor classes would offer the same argument:

struct Logger
{
    void operator()(const string &message) { Log(message); }
}

I'd say there's no one feature of C++ that implies you should use a class instead of a struct (except maybe virtual functions). It's all about what interface you want to present - use a struct if you are ok with everything being public. If you find you are wanting to add private: sections, that a good sign you really want a class instead of a struct.



回答12:

If there's accesser methods for the data members then it's a class.

If you have direct access to the data and can modify it at will, it's a struct.

There's no reason a struct shouldn't have constructors, comparison operators, etc.



回答13:

The only methods I like to put on structs are property-type methods, simple transforms (and, if appropriate for the type, operators), and non-default constructors.

Properties

For instance, I might define a RECT struct as below:

typedef struct tagRECT{
    int left;
    int top;
    int right;
    int bottom;

    int get_width(){return right - left;}
    int get_height(){return bottom - top;}
    int set_width(int width){right = left + width; return width;}
    int set_height(int height){bottom = top + height; return height;}
} RECT, *PRECT;

The "set" methods return the new value to support assignment chaining in the case that the compiler supports properties as an extension.

Simple transforms

For POD types that store complex data, I might include methods that perform simple transformations on that data. An obvious example might be including Rotate, Scale, Shear, and Translate methods on a TransformationMatrix struct.

Operators

Really just an extension of the above, if operators make sense for the type, I will add them to a struct. This can be appropriate and necessary to maintain the atomicity of the object. An obvious example is standard arithmetic operators on a Complex struct.

Constructors

I prefer not to have a default constructor on my structs. I don't want to allocate an array of PODs and suffer the invocation of a thousand (or whatever) default initializations. If memset initialization won't suffice, I'll provide an Initialize method.

I will, however, provide a non-default constructor on structs. This is especially useful when one or more fields can be inferred from partial construction.



回答14:

I always use structs for 'lumps of data', even if they get decorated with a constructor and sometimes comparison operators, they never get methods added to them (not even get/set methods).

I think this shows the intent of the type - a struct is just data to be operated on by other things.



回答15:

I use struct whenever I want to use data member as interface of the object. I'd change the struct to a class, whenever there is a need to add a private section.



回答16:

Generally, I use class whenever I need to use access specifiers. This has the effect that most of my top-level stuff remain as classes, while the rare POD collections and my not-so-rare inner-class pimpls are usually structs.



回答17:

My simple rule of thumb for structs and classes:

if (data_requires_strict_alignment == TRUE) {
  use(struct);
} else {
  use(class);
}

That is, if the data you are representing corresponds to some data object that has strict member order and alignment requirements (for example, a data structure exchanged with hardware on the driver level), use a struct. For all other cases, use a class. Classes have so many features and capabilities that structs do not, and in my experiences it is beneficial to use classes whenever possible, even if you are not using any of those additional features at the moment (if nothing else, a data-only class is like a struct with a safer default access level). Reserve structs for those cases when you need the unique properties of a struct; namely, the ability to specify structure members in a precise order and with precise alignment/padding (typically with low-level communication, e.g. drivers) such that you can cast it to a byte array, use memcpy() on it, etc. If you follow this model, then a class would not be used as a member of a structure (since a class definition does not specify alignment or a predictable value for sizeof(class)). Likewise, if you are thinking about constructors or operators, use a class.

This rule of thumb also helps make it easier to interface with C code, since structs are used in a manner consistent with C structures.



回答18:

I wasn't making a distinction between member and free functions when I was thinking about the question, but I now see that this was a mistake. It now seems to me that structs should only rarely have member functions. It is pretty clear that everything in a struct should be public.

Therefore, there is generally no point in having struct member functions because any function can change the data in the struct. A member function on a struct would be a convenience rather than a necessity.

The only exceptions would be things required to be member functions for some other purpose - constructors for initialising arrays; comparisons for use with a map; things used by templates; etc.

Perhaps class and struct should be seen as opposites - a class exposes functions and hides data, whereas a struct exposes data and allows you to hide functions.

Going back to the byte swapping example:

struct S
{
    int data;
    S() {data = 1234;}
    void ByteSwap() {network::ByteSwap( &data );}
};

S s;
s.ByteSwap();

would become:

struct S
{
    S() {data = 1234;}
    int data;
};

namespace network
{
    void ByteSwap( int* i ) {/*byte swap *i*/}
    void ByteSwap( S* s ) {ByteSwap( &s->data );}

    S s;
    ByteSwap(&s);
}

This makes sense when the data and some functions do not always strongly belong together. Byte swapping would only be of interest to the network system but higher level functions can still use the struct without even knowing about low level stuff like byte swapping.

Another benefit in this case is that the related byte swapping operations are all kept together in the same place.



回答19:

This is purely a matter of style, and what is right or wrong will be dictated by your shop. Sometimes the decision will be to make no decision but, to paraphrase Rush, they still will have made a choice.

As a rule of thumb I will generally use structs for simpler datatypes rangin from PODs to objects that have member simple functions. But there is no bright line, and once I define a struct I will not normally go back and change it to a class just because I added more functionality. I see no value in that.



标签: c++ oop struct