views:

966

answers:

9

Where I work, people mostly think that objects are best initialised using C++-style construction (with parentheses), whereas primitive types should be initialised with the = operator:

std::string strFoo( "Foo" );
int nBar = 5;

Nobody seems to be able to explain why they prefer things this way, though. I can see that std::string = "Foo"; would be inefficient because it would involve an extra copy, but what's wrong with just banishing the = operator altogether and using parentheses everywhere?

Is it a common convention? What's the thinking behind it?

A: 

It's an issue of style. Even the statement that "std::string = "Foo"; would be inefficient because it would involve an extra copy" is not correct. This "extra copy" is removed by the compiler.

Andrew Stein
+12  A: 

Initializing variables with the = operator or with a constructor call are semantically the same, it's just a question of style. I prefer the = operator, since it reads more naturally.

Using the = operator usually does not generate an extra copy - it just calls the normal constructor. Note, however, that with non-primitive types, this is only for initializations that occur at the same time as the declarations. Compare:

std::string strFooA("Foo");  // Calls std::string(const char*) constructor
std::string strFoo = "Foo";  // Calls std::string(const char*) constructor
                             // This is a valid (and standard) compiler optimization.

std::string strFoo;  // Calls std::string() default constructor
strFoo = "Foo";      // Calls std::string::operator = (const char*)

When you have non-trivial default constructors, the latter construction can be slightly more inefficient.

The C++ standard, section 8.5, paragraph 14 states:

Otherwise (i.e., for the remaining copy-initialization cases), a temporary is created. User-defined conversion sequences that can convert from the source type to the destination type or a derived class thereof are enumerated (13.3.1.4), and the best one is chosen through overload resolution (13.3). The user-defined conversion so selected is called to convert the initializer expression into a temporary, whose type is the type returned by the call of the user-defined conversion function, with the cv-qualifiers of the destination type. If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The object being initialized is then direct-initialized from the temporary according to the rules above.87) In certain cases, an implementation is permitted to eliminate the temporary by initializing the object directly; see 12.2.

Part of section 12.2 states:

Even when the creation of the temporary object is avoided, all the semantic restrictions must be respected as if the temporary object was created. [Example: even if the copy constructor is not called, all the semantic restrictions, such as accessibility (11), shall be satisfied. ]

Adam Rosenfield
using = will create a temporary string,and will initialize the object using that temporary string with the copy constructor.(this is the reason that form is called copy initialization,and the string o("foo") is called direct-initialization).another diff is that the = "foo" requires an implicit ctor.
Johannes Schaub - litb
however, the compiler is allowed to eliminate the temporary that is created and passed to the copy constructor. so you may not notice the temporary being created.
Johannes Schaub - litb
Herb Sutter's article on 'direct initialization' vs. 'copy initialization': http://www.gotw.ca/gotw/036.htm
Michael Burr
@litb: The compiler is allowed to optimize away the construction of the temporary completely and just call the normal constructor.
Martin York
It's not required to do so, though, so the second case above is permitted to call the copy constructor, as well as the (const char*) constructor, and then the destructor on the temporary. But you'd complain to the compiler vendor if it did...
Steve Jessop
it also has to check the visibility of the copy constructor. if it is not accessible the = form is invalid indeed :)
Johannes Schaub - litb
@Mike B: wish I could up vote comments...
ceretullis
+2  A: 

You will probably find that code such as

std::string strFoo = "Foo";

will avoid doing an extra copy and compiles to the same code (a call of a single-argument constructor) as the one with parentheses.

On the other hand, there are cases where one must use parentheses, such as a constructor member initialisation list.

I think the use of = or parentheses to construct local variables is largely a matter of personal choice.

Greg Hewgill
+1  A: 

Well, who knows what they think, but I also prefer the = for primitive types, mainly because they are not objects, and because that's the "usual" way to initialize them.

hasen j
In C++, objects of primitive type are objects, they just aren't class objects.
Steve Jessop
what? since when? I'm talking about objects in the OOP sense. C++ primitive types are definitely not objects.
hasen j
yes. everything except references, function and void types are objects.
Johannes Schaub - litb
I meant by the defn of "object" in the C++ standard. I think I agree they're at best degenerate objects, OOP-wise: their only 'members' are operators. But I don't see any relevance of that to how they're initialised: I think it's purely because it's the "usual way", inherited from C.
Steve Jessop
+1  A: 

Unless you've proven that it matters with respect to performance, I wouldn't worry about an extra copy using the assignment operator in your example (std::string foo = "Foo";). I'd be pretty surprised if that copy even exists once you look at the optimized code, I believe that will actually call the appropriate parameterized constructor.

In answer to your question, yes, I'd say that it's a pretty common convention. Classically, people have used assignment to initialize built-in types, and there isn't a compelling reason to change the tradition. Readability and habit are perfectly valid reasons for this convention given how little impact it has on the ultimate code.

Greg D
Ah! Yes, that'll be it: in C, the = operator was the only way, so it still "feels right" to old-timers. Thanks.
Tommy Herbert
A: 

I believe that is more of a habit, very few objects could be initialized using = , the string is one of them. It's also a way of doing what you said "using parenthesis everywhere (that the language allows you to use it)"

+8  A: 

I just felt the need for another silly litb post.

string str1 = "foo";

is called copy-initialization, because what the compiler does, if it doesn't elide any temporaries, is:

string str1(string("foo"));

beside checking that the conversion constructor used is implicit. In fact, all implicit conversions are defined by the standard in terms of copy initialization. It is said that an implicit conversion from type U to type T is valid, if

T t = u; // u of type U

is valid.

In constrast,

string str1("foo");

is doing exactly what is written, and is called direct initialization. It also works with explicit constructors.

By the way, you can disable eliding of temporaries by using -fno-elide-constructors:

-fno-elide-constructors
    The C++ standard allows an implementation to omit creating a temporary which 
    is only used to initialize another object of the same type. Specifying this 
    option disables that optimization, and forces G++ to call the copy constructor 
    in all cases.


The Standard says there is practically no difference between

T a = u;

and

T a(u);

if T and the type of u are primitive types. So you may use both forms. I think that it's just the style of it that makes people use the first form rather than the second.


Some people may use the first in some situation, because they want to disambiguate the declaration:

T u(v(a));

migh look to someone as a definition of a variable u that is initialized using a temporary of a type v that gets a parameter for its constructor called a. But in fact, what the compiler does with that is this:

T u(v a);

It creates a function declaration that takes a argument of type v, and with a parameter called a. So people do

T u = v(a);

to disambiguate that, even though they could have done

T u((v(a)));

too, because there are never parentheses around function parameters, the compiler would read it as a variable definition instead of a function declaration too :)

Johannes Schaub - litb
Historical: In the case of primitive types, T a = u was the only allowed form for a long time. The second form T a(u) was introduced into the standard to be used in initialization lists, and for primitive types is equivalent.
David Rodríguez - dribeas
A: 

One argument that one could make for:

std::string foo("bar");

Is that it keeps things the same even if the argument count changes, i.e.:

std::string foo("bar", 5);

Doesn't work with a '=' sign.

Another thing is that for many objects a '=' feels unnatural, for example say you have a Array class where the argument gives the length:

Array arr = 5;

Doesn't feel good, since we don't construct an Array with the value 5, but with length 5:

Array arr(5);

feels more natural, since you are constructing an object with the given parameter, not just copying a value.

Grumbel
The 'explicit' keyword is used for just that purpose - to make sure that "Array arr = 5" doesn't compile. You have to say "Array arr(5)" if the Array(int) constructor is declared explicit.
Adam Rosenfield
A: 

But then just to confuse you even more you initialize primitives in the initialization list using object syntax.

foo::foo()   
  ,anInt(0)   
  ,aFloat(0.0)   
{   
}
Martin Beckett