17

Edit: Just to be clear, the struct doesn't do anything, as in it has no functions. I think I gave the impression that I thought using an initialiser list and leaving the body of the constructor empty was the issue at hand.

Say I'm using a struct to hold two values, and I have a constructor just so I can create an arbitrary struct like this:

struct twoValues
{
    int x;
    int y;

    twoValues(int x_, int y_):y(y_),x(x_)
    {}
};

someFunction(twoValues(1,2));

That saves me from having to do this:

twoValues anInstance;
anInstance.x=1;
anInstance.y=2;
someFunction(anInstance);

Edit: You're all correct, I could also initialise with the following:

twoValues anInstance = {1,2};

I see nothing wrong with this but I had some feedback from a C++ test and one of the negative feedback marks was "constructors for structs that don't do anything". I had limited contact with the guy testing me and so never asked why.

Is it a bad thing and why? I would rather carry on doing it.

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
cool mr croc
  • 725
  • 1
  • 13
  • 33

8 Answers8

17

It depends on what the struct is being used for. As others have said, the constructor means that the class is no longer a POD, and that aggregate initialization cannot be used for it. In particular, you cannot have something at namespace scope like:

TwoValues const table[] =
{
    { 1, 2 },
    { 3, 4 },
    // ...
};

You can have:

TwoValues const table[] =
{
    TwoValues(  1, 2  ),
    TwoValues(  3, 4  ),
    // ...
};

but it is more verbose, and it implies dynamic initialization, which may result in order of initialization issues.

On the other hand, without the constructor, you cannot create temporary instances on the fly. Instead of:

extern void f( TwoValues const& );
//  ...

f( TwoValues( 1, 2 ) );

you have to write:

extern void f( TwoValues const& );
//  ...

TwoValues tmp = { 1, 2 };
f( tmp );

If the object is dynamically allocated, it's even worse, since you either have to allocate first, then initialize, or create a temporary as above, and then write new TwoValues( tmp ) and use the implicit copy constructor.

You have to choose. Depending on what the struct is used for, one or the other will be preferred; on one hand, I have a lot of structs which are used exclusively in static tables, and intentionally don't have a constructor (and contain only types which support static initialization), and use them a lot for configuring code. On the other hand, I also have a lot of structs which are internal to a class, along the lines of Node in a tree or a graph; these almost always have a constructor, to facilitate creating them on the fly. There's no "correct" answer without knowing the role of the struct in your application.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • 1
    if you are going to write the answer this way you should mention the new features in C++11, otherwise this post will just be gibberish in a few months (especially since you are not stating that what is being written applies to C++03, we need to be explicit in cases as this) – Filip Roséen - refp Jul 18 '12 at 10:58
  • I added an header to your post stating that it's only relevant to C++03 – Filip Roséen - refp Jul 18 '12 at 11:08
  • 1
    @refp I removed the misleading header you added. I'm not sure which point you were talking about: C++11 does add some additional initialization syntax, but the distinctions concerning dynamic and static initialization haven't changed, and it will be at least 5 years before most programmers can use any of the new syntax. – James Kanze Jul 18 '12 at 12:48
  • 1
    that you have to write `TwoValues tmp = {1,2}; f (tmp);` (among other things) that is not the case in C++11 – Filip Roséen - refp Jul 18 '12 at 12:54
  • Didn't C++11 relax the rules so that a constructor doesn't disqualify a `struct` as being POD? – dan04 Jul 18 '12 at 14:11
  • @dan04: No it didn't, are you thinking of the new concept of a standard layout type? – CB Bailey Jul 18 '12 at 15:27
  • What C++11 added was this: `f(TwoValues{1, 2});` - a syntax for creating POD temporaries. – orlp Jul 18 '12 at 16:49
  • @nightcracker Yes, and I guess I should have mentioned this. But since I don't have access to a reliable C++11 compiler, I've no experience with such things, and would have had to look the information up. Perhaps in the future (say in 5 years time), I'll start using the new syntax, and drop writing constructors for such classes completely. (Or perhaps I'll just be retired.) – James Kanze Jul 18 '12 at 17:20
13

Declaring an empty constructor has side-effects..

Even though your constructor has an empty body it is still considered to be a constructor, and therefore certain object properties will be lost - such as the object being POD.

Some cases require the use of Plain Old Data-types, which can make it undesirable to do what you've done without a specific reason.

Read more about initialization without a defult constructor in the next section.


&&

You can initialize your members with values without explicitly defining a "constructor that doesn't do anything", just use the brace-initialization syntax as in the below snippet.

struct Obj {
  int x, y;
};

Obj a = {1,3}; /* a.x = 1, a.y = 2 */

The downside of not having a constructor is that you cannot initialize the object using = { ... } in certain circumstances when writing C++03.

C++11 fixes this for you, see the next section for relevant examples.


In C++11 the initialization using braces ( = { ... }) has been given increased functionality.

As seen in the below snippet where the defined constructor of Obj is called even though we use the same form of initialization as earlier in this post.

struct DoubleBoth {
  DoubleBoth (int x, int y)
    : x(x*2), y(y*2) 
  {}  

  int x, y;
};

The snippets below were all illegal prior to C++11:

DoubleBoth a = {1,2}; /* a.x = 2, a.y = 4 */

struct Wrapper {
  Wrapper ()
    : value {3,4}
  {}

  DoubleBoth value;
};

void func (DoubleBoth v = {1,2}) {  // c++11 only
  ...
}

func ({4,5}); // c++11 only, c++03 requires `DoubleBoth obj (0,1); func (obj);`
Community
  • 1
  • 1
Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
7

You've stopped twoValues from being a POD and have prevented instances of the struct from being default- or value- initialized which are often desirable properties. Personally, I would prefer to have a free function for making temporary instances if you need a simple C++03 friendly approach.

E.g.

twoValues makeTwoValues(int x_, int y_)
{
    twoValues tmp = { x_, y_ };
    return tmp;
}

void f() {
    someFunction(makeTwoValues(1,2));
}

E.g. initializing a member of type twoValues

class X {
    twoValues tv;
public:
    X(int x, int y) : tv(makeTwoValues(x, y)) {}
};
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • “which are often desirable properties” – you might want to expand on that – in particular, since it has distinct disadvantages as well (at least in C++0x, see juan’s answer) – Konrad Rudolph Jul 18 '12 at 08:45
  • @KonradRudolph: I don't see the disadvantage (other than having to write the function), can you expand? – CB Bailey Jul 18 '12 at 08:51
  • the statement about it no longer being able to be *default initialized* is wrong, constructors can have default arguments so using that will make it *default initializable*, even though a constructor has been defined. – Filip Roséen - refp Jul 18 '12 at 09:02
  • @refp: But the constructor in the question doesn't have default arguments so my statement is correct. You can add defaults for the constructor parameters and you can add a user-declared default constructor but I was answering the question as asked. – CB Bailey Jul 18 '12 at 09:18
  • 2
    @Charles Just that you have to write a function. Not a big disadvantage, but after all that’s usually what a constructor offers you, the free function is a kind-of ugly workaround where you’re programming a constructor-like thing to avoid having a constructor. Conceptually, this is just iffy even though I understand that it may be desirable to preserve PODness. – Konrad Rudolph Jul 18 '12 at 09:18
  • 1
    @KonradRudolph: I would argue that it's not a workaround. It's a positive choice to make a type that is an aggregate and a function to create initialized values of that type. It's especially useful if you want C compatibility but it's also useful if you want to maintain all of the potential POD optimized algorithms and retain the ability to create cheap uninitialized instances even in a C++ only environment. – CB Bailey Jul 18 '12 at 09:25
7

It's much safer to have this kind of constructor, than to initialize with a list.

When you initialize with a list :

twoValues anInstance = {1, 2};

It really depends on the order of the members in the struct. Which in most cases is totaly random to begin with.

If another programmer happens to add another member, or to sort the members by alphabet, it wont work correctly.

Having a constructor that assigns the values to the right member by NAME is much safer.

Yochai Timmer
  • 48,127
  • 24
  • 147
  • 185
  • Good point. Developers know that messing with the order of arguments in a function or constructor is sensitive. But order of members in a struct not so much, which is why relying on that order is dangerous, especially in C++ – tenfour Jul 18 '12 at 08:52
  • The order of members should never be "totally random" unles the class author is drinking on the job and developers who sort member alphabetically should be shot, especially if they do it to structs where layout is part of the interface. – Jonathan Wakely Jul 18 '12 at 08:57
  • Right. But it's hard to find someone to shoot them, so that kinda stuff happens. – Yochai Timmer Jul 18 '12 at 09:00
4

Probably because you can initialize it like this:

twoValues anInstance = {1, 2};

However, without the constructor you cannot initialize a anInstance in another struct's initializer list in C++03. For example:

struct Bar {
  twoValues v_;
  Bar() : v_(1,2) {} // error!
  Bar() { v_ = {1,2}; } // have to assign value in constructor body
  Bar() : v_{1,2} {} // OK in C++11
};

so in fact the constructor does something, and it does serve a very useful purpose in C++03. It is less of an issue in C++11.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • 2
    *"without the constructor you cannot initialize a anInstance in another struct's initializer list"*. Actually he can, but for that he needs to write some additional code (in C++03). And in C++11, he can write `Bar() : v_{1,2} {}` . See this : http://ideone.com/Pn1rA – Nawaz Jul 18 '12 at 08:52
2

There is nothing wrong with this contructor.

Also, it does something, it affect values to x and y, which is what I would expect from such a constructor.

But, maybe there are some coding rules in your company that says that you should do it another way. In that case I would recommend you ask the person that gave you the feedback and make the necessary modifications.

Mesop
  • 5,187
  • 4
  • 25
  • 42
2

The constructor makes the struct a non-POD which may not be desirable in some cases.

If your struct doesn't have constructor, then your struct will be POD1, and you will be allowed to initialize like this:

twoValues anInstance = {1,2}; //C++03 and C++11 both!
twoValues anInstance {1,2};   //C++11 only

which are good.

1. Actually there are other things which make a struct non-POD. So just because your struct doesn't have constructor doesn't necessarily mean it is POD. You would like to see this post to know what those other things are.

Community
  • 1
  • 1
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • And it makes the struct non-default-constructible, which is like a bad idea. – rodrigo Jul 18 '12 at 08:45
  • 2
    @rodrigo No, not necessarily. In fact, unless *explicitly* required, you should *not* make classes default-constructible. – Konrad Rudolph Jul 18 '12 at 08:47
  • @KonradRudolph: Agreed, but it is not a class, it is a struct with all public member variables (no invariant: the most likely use is just a data aggregation). With a type like this I see little gain in not being default-constructible. – rodrigo Jul 18 '12 at 08:53
  • The initialization (at least in C++11) relies on the type being aggregate, not necessarily POD. – juanchopanza Jul 18 '12 at 08:57
  • @juanchopanza: Well, in C++11, the type doesn't even need to be aggregate if its constructor takes `std::initializer_list`. (as for your confusion, POD is a kind of aggregate, and struct can be POD). – Nawaz Jul 18 '12 at 08:59
  • Sticking to aggregate initialization, an aggregate doesn't have to contain PODs, whereas I thought the definition of POD was recursive. – juanchopanza Jul 18 '12 at 09:01
  • @juanchopanza: *"whereas I thought the definition of POD was recursive"*. No. – Nawaz Jul 18 '12 at 09:03
0

Structures can have constructors, and the syntax is the same as for classes in C++. The difference between a class and a struct is that class-members are private by default, while struct-members default to public, however Unions will not allow constructors in the structs. You can make a constructor on the union though.

NIlesh Sharma
  • 5,445
  • 6
  • 36
  • 53