Skip to main content
gamedev.net
🔒 Locked

Constructors

Started by FrigidHelix 3 replies 450+ views
Original Post
FrigidHelix
FrigidHelix
I thought once a default constructor was defined, IT was used whenever creating new instances of the class. However, when the constructor takes no arguments, I do not get this behavior. Consider these situations, where the class MyClass is defined:
  
/* Having the empty argument list should be to create this instance (correct me if I''m wrong), however, in this case, the constructor does NOT GET CALLED */
MyClass var1();

/* Instead, I need to write it like this (below) to get it to call the constructor */
MyClass var2; // NO PARENTHESIS

  
The constructor is ALWAYS skipped if i use the parenthesis notation. Even if the constructor is made private (in which case it should give a compile time error when I try to instantiate one), the constructor is skipped. Why is this??? Here is a full source example that shows the problem:
  
class MyClass
{
	public:
		MyClass() { cout << "myClass()" << endl; }
};

int main(int argc, char** argv)
{
	MyClass myClass1(), myClass2;
 
	// myClass1 will call the constructor (and output 1 line), whereas

	// myClass2 will not call the constructor (nothing outputted).


	return 0;
}
  
DrPizza
DrPizza
The compiler attempts to treat things as type declaration unless it has reason not to. It''s vexing, it''s thanks to c that the grammar contains this ambiguity.


Foo foo1; // default constructor call
Foo foo2(); // function declaration; foo2 takes no parameters and returns a Foo by value


That''s just the tip of the iceberg. Several bits of C legacy conspire to make all sorts of innocuous-looking statements do something completely unexpected.

From Effective STL:

int f1(double d); // declare a function f1 returning int, taking a double
int f2(double (d)); // declare a function f2 returning int, taking a double;
// we can put in extra brackets around parameter names
// and they get ignored
int f3(double); // declare a function f3 returning int, taking a double; we
// can omit parameter names

int g1(double (*pf)()); // declare a function g1 returning int, taking a
// pointer to a function returning double, taking
// void
int g2(double pf()); // declare a function g2 returning int taking a pointer
// to a function returning double, taking void; non-
// pointer syntax
int g3(double ()); // declare a function g3 returning int, taking a pointer
// to a function returning double, taking void; we can
// still omit parameter names

We notice how parameters around a parameter name are ignored, but parameters on their own mean parameter list, i.e. a function or function pointer type.

One last thing to note -- when creating an anonymous default initialized Foo object, we do put the brackets in for the constructor call.

Well, "so what?" you say. The problem is if we want our function to take not double but instead some user-defined type.

For a (contrived) example, imagine we want to copy construct a Foo, with a default-initialized anonymous Foo as the argument.

Foo foo(Foo()); // Foo() creates an anonymous default constructed Foo;
// this is passed to the copy constructor creating foo...
// right?

Well, unfortunately, that''s not what happens. Remember -- empty brackets mean a parameter list. Foo() gets interpreted as a function pointer definition. We''re declaring a function foo that returns Foo and takes a pointer to a function returning Foo and taking void.

Another complication; imagine that Foo() has a constructor taking an int, and we have an int named i. We wish to pass to our copy constructor not a default Foo, but a Foo constructed with i as its parameter.

So we try:

Foo foo(Foo(i));

But oh dear. Again the compiler tries to treat this as a function declaration. Remember, brackets around parameter names are ignored, as are parameter names. This is treated as a declaration of a function foo returning Foo and taking a Foo.

Now this might seem all rather esoteric -- this kind of thing doesn''t happen in real life, does it?

Well, unfortunately, yes. The commonest case is probably the one you''ve run into. The simple attempt to create a default constructed object with the extra round brackets. Since empty round brackets mean parameter list, it''s treated as a function declaration.

But that''s not the only time it occurs.

Imagine, I want to create a list<int>, and I want to populate that list with integers read from an istream. To do this I will use the list constructor that looks like:

template
list(InputIterator first, InputIterator last);

and istream_iterator<int>s to actually iterate over my istream. Their constructors look like:

istream_iterator(istream&); // this creates the ''start'' iterator
istream_iterator(); // this creates an end sentinel iterator


With that knowledge in mind, I try to create my list:

list myList(istream_iterator(myIstream), istream_iterator());

The first parameter should be an anonymous istream_iterator called with the istream& constructor; the second should be an anonymous istream_iterator called with the default constructor.

Except it doesn''t work.

Yet again, it declares a function.

The function returns list<int>. Its name is myList. Its first parameter is of type istream_iterator<int> -- remember, parameter names are ignored, as are brackets around parameter names. The second parameter is a pointer to a function returning istream_iterator<int> and taking no parameters.

Now that is rather annoying.

How do we fix it?

First a quick note about compilers; VC++ 6 and I think maybe even 7 (though not 7.1) get this wrong; they''ll treat the above in the expected way -- as a constructor call. Whilst this appears to make more sense, it is incorrect. VC++ 7.1 and g++ get it right.

What we need to do is to make the compiler stop trying to treat these things as types and instead treat them as expressions.

It happens that we can do that by adding an extra set of round brackets. If we put the round brackets around the entire constructor call, the compiler doesn''t treat it as a type but instead as an expression.

So

list myList((istream_iterator(myIstream)), istream_iterator());

should work. We only need to do it to the first argument; once it''s stopped trying to treat it as a function declaration, it gets the rest of it right.

Except compiler bugs bite us again. This time g++ -- the extra set of brackets causes a parse error. We have a couple of choices if we want to write portable code, but neither is particularly satisfactory.

We can introduce a g++-specific workaround. We can make its parser get its act together by making the expression a little more complicated:

list myList((0, istream_iterator(myIstream)), istream_iterator());

The expression (0, istream_iterator<int>(myIstream)) evaluates to the anonymous istream_iterator; the 0, makes g++ realize that this is an expression, not a type.

The downsides to this workaround are that it''s ugly, and that it exposes a problem with VC++''s compiler. In particular, the 0 (for some unfathomable reason) prevents a certain optimization (called the Return Value Optimization, RVO) from occurring.

The other workaround is to not use anonymous objects at all; but that''s also displeasing, because again it prevents the RVO from occurring, and also requires us to name these objects that don''t really deserve names.


So -- unless you are creating an anonymous default-constructed object -- don''t put in the brackets. And when you are creating an anonymous object (default constructed or otherwise), put in extra brackets.
char a[99999],*p=a;int main(int c,char**V){char*v=c>0?1[V]:(char*)V;if(c>=0)for(;*v&&93!=*v;){62==*v&&++p||60==*v&&--p||43==*v&&++*p||45==*v&&--*p||44==*v&&(*p=getchar())||46==*v&&putchar(*p)||91==*v&&(*p&&main(0,(char**)(--v+2))||(v=(char*)main(-1,(char**)++v)-1));++v;}else for(c=1;c;c+=(91==*v)-(93==*v),++v);return(int)v;} /*** drpizza@battleaxe.net ***/
FrigidHelix
FrigidHelix
thanks a lot guys. makes sense now.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.