18

Considering the cost, are these cases the same?

// case 1
int a = 5;

// case 2
int a (5);

// case 3
int a;
a = 5
Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
zhanwu
  • 1,508
  • 5
  • 16
  • 27

5 Answers5

20

The three syntaxes are different, bear with me while I use a user defined type instead of int, I will go back to int later.

T a(5);     // Direct initialization
T b = 5;    // Implicit conversion (5->tmp) + copy-initialization
T c; c = 5; // Default initialization + assignment

In the first case the object a is constructed by means of a constructor that takes an int or a type that can be implicitly converted from int.

struct T {
  T( int ); // T a(5) will call this directly
};

In the second case, a temporary object of type T is created by an implicit conversion from int, and then that temporary is used to copy construct b. The compiler is allowed to optimize the code away and perform just the implicit conversion in place of the final object (instead of using it to create the temporary. But all restrictions have to be verified:

class T {
   T( T const & );
public:
   explicit implicit T( int );
};
int main() {
   T b = 5;   // Error 1: No implicit conversion from int to T.
              //     Fix: remove the `explicit` from the constructor
              // Error 2: Copy constructor is not accessible
}

The third case is default construction followed by assignment. The requirements on the type are that it can be default constructed (there is a constructor with no arguments, or there is no user defined constructor at all and the compiler will implicitly define it). The type must be assignable from int or there must be an implicit conversion from int to a type U that can be assigned to T. As you see, the requirements for the type in the tree cases differ.

Besides the semantics of the different operations, there is other important difference, not all of them can be used in all of the contexts. In particular, in an initialization list in a class you cannot use the implicit convert + copy initialize version, and you can only have the first half of default construct + assign.

// OK                     // error                  // ok but different
struct test {             struct test {             struct test {
   T x;                      T x;                      T x;
   test(int v) : x(v) {}     test(int v) : x=5 {}      test( int v ) {
                                                          x = v;
                                                        }

In the first case the attribute x is directly initialized with the value v. The second case is a syntax error. The third case first default initializes and then assigns inside the body of the constructor.

Going back to the int example, all of the requirements are met by the type, so there is almost no difference on the code that the compiler generates for the three cases, but still you cannot use the int b = 5; version inside an initializer list to initialize an integer attribute. Moreover, if a class has a member attribute that is a constant integer, then you cannot use the equivalent of int c; c =5; (third column above) as the member attribute becomes const when it enters the constructor block, that is, x = v; above would be trying to modify a constant and the compiler will complain.

As to the cost that each one has, if they can be used at all, they incur the same cost for an int (for any POD type) but not so for user defined types that have a default constructor, in which case T c; c = 5; will incur the cost of default construction followed by the cost of assignment. In the other two cases, the standard explicitly states that the compiler is allowed to generate the exact same code (once the constraints are checked).

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 2
    OP's example is about `int`. User-defined types may not ( and often do not) behave in the same way as built-in types do. For example as we've see here [Undefined Behavior and Sequence Points Reloaded](http://stackoverflow.com/questions/4638364/undefined-behavior-and-sequence-points-reloaded) – Nawaz Mar 18 '11 at 15:35
  • 1
    @Nawaz: I don't quite follow your comment around the behavior. Regarding the answer, I know it diverges from the original question in that a) it first provides the semantic differences between the different syntaxes --and that has to be done with types where the differences are noticeable--, and b) the result of the last part is that the differences in the semantics are not related to performance (original question) but with usability (when and where can you use one or another), which in fact remarks an issue in your answer: both are NOT exactly the same, as the first cannot be used everywhere – David Rodríguez - dribeas Mar 18 '11 at 16:56
  • 1
    Now on whether user defined types and built-in types, while they are not the same, and it would be impossible, one of the focus in the design of the language was to make them as close as possible. They differ in what they have, but the language is designed to minimize the differences, if you check the standard you will see that unlike Java or C#, instances of *any* type are regarded as *object*, and whenever possible they are treated equally. I would have to locate a quote from Bjarne in Design and Evolution of C++ to paste here. – David Rodríguez - dribeas Mar 18 '11 at 17:00
  • 1
    The standard clearly states that for non-class types the effect of both initializations is the same, but that does not make the the same initialization, as the restrictions in the grammar limit the initialization with the `=` sign in some contexts. I do agree that this answer probably goes way beyond the concrete question being asked, but I believe it adds value. – David Rodríguez - dribeas Mar 18 '11 at 17:08
19

First and second are exactly same as both are initialization. Third one is different, as this is assignment. These differences are as per the C++ Standard. However, the compiler can treat all three as same!

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • theoretically yes, but looks like the compiler handled them in the same way (in case of type int) – zhanwu Mar 18 '11 at 09:52
5

For the first two, there will be no difference.

From Standard docs, 8.5.11,

The form of initialization (using parentheses or =) is generally insignificant, but does matter when the initializer or the entity being initialized has a class type; see below. A parenthesized initializer can be a list of expressions only when the entity being initialized has a class type.

The third one is not an initialization but an assignment.

And considering the cost,

In the first two cases, you are creating an integer with a value 5.

In the third case, you are creating an integer with an undefined value and replace it with 5..

liaK
  • 11,422
  • 11
  • 48
  • 73
  • 1
    Actually I start thinking about this when reading "effective c++" about initializing object. And I thought the same way as you, but I was unsure. Looks like I choose a wrong example, using 'int', because the compiler is optimized. However, considering any user defined class, you are right. – zhanwu Mar 18 '11 at 09:59
4

If you use an optimizing compiler, they will all compile to the same code. So they all have the same cost.

Gunther Piez
  • 29,760
  • 6
  • 71
  • 103
2

Yes, they all evaluate to the exact same assembler representation. You can test this e.g. with GCC by writing a dummy function and then producing the assembler output: g++ -S file.cxx -o file.s

DarkDust
  • 90,870
  • 19
  • 190
  • 224
  • 1
    Why even have such type of declaration, "int a (5)"? Thanks. – Shamim Hafiz - MSFT Mar 18 '11 at 09:34
  • 2
    @Gunner: I guess for consistency reasons (initialize C++ objects on stack), but I don't really know. – DarkDust Mar 18 '11 at 09:38
  • The "function call" initialization syntax exists because that is how you construct instances where the class constructor takes multiple arguments. It would be strange if this same syntax could not be applied to constructors with only one argument or to built in types. – Stewart Mar 18 '11 at 09:40
  • 1
    @DarkDust checked, they produce exactly the same assembler code using g++ 4.4.3 – zhanwu Mar 18 '11 at 09:50
  • 3
    If it is used in a template, you don't have to know if it is an int or a class type. The same initialization syntax works for both. – Bo Persson Mar 18 '11 at 09:57
  • 3
    @Gunner: Basically you cannot use assignment in the initialization list of a class, and that forced the adoption of the *constructor-like* syntax for all types, including basic types. Consider: `struct test { const int k; test( int v ) : k(v) {} };` When the curly braces are entered k has already been turned into a constant and is not modifyable (`k = v;` in the body of the constructor will fail) – David Rodríguez - dribeas Mar 18 '11 at 10:23
  • 2
    g++ --dump-tree-gimple file.cpp, shows this a little clearer than reading assembler. – Chris Huang-Leaver Mar 18 '11 at 10:31
  • 1
    @ David Rodríguez - dribeas: Thanks, this makes lot sense now :) – Shamim Hafiz - MSFT Mar 18 '11 at 10:51