6

Assume that the following code is legal code that compiles properly, that T is a type name, and that x is the name of a variable.

Syntax one:

T a(x);

Syntax two:

T a = x;

Do the exact semantics of these two expressions ever differ? If so, under what circumstances?

If these two expressions ever do have different semantics I'm also really curious about which part of the standard talks about this.

Also, if there is a special case when T is the name of a scalar type (aka, int, long, double, etc...), what are the differences when T is a scalar type vs. a non-scalar type?

Omnifarious
  • 54,333
  • 19
  • 131
  • 194

5 Answers5

3

Yes. If the type of x is not T, then the second example expands to T a = T(x). This requires that T(T const&) is public. The first example doesn't invoke the copy constructor.

After the accessibility has been checked, the copy can be eliminated (as Tony pointed out). However, it cannot be eliminated before checking accessibility.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • 1
    The bit about checking the accessibility of the copy constructor is important. Another thing that can happen (because I just tested it) is that you have a 'copy constructor' that takes a non-const reference as an argument, and that will cause it to fail too. – Omnifarious Feb 21 '11 at 10:08
  • @Omnifarious: good point, that's another reason. The `T(x)` expression is a temporary and won't bind to a `T&`. That's intentional, there's no way your `T::T(T& victim)` could change the `x` object. – MSalters Feb 21 '11 at 14:07
2

From 8.5.14 (emphasis mine):

The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see class.temporary, class.copy.

So, whether they're equivalent is left to the implementation.

8.5.11 is also relevant, but only in confirming that there can be a difference:

-11- The form of initialization (using parentheses or =) is generally insignificant, but does matter when 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.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • If there is a copy constructor that does something funky (and therefor may not actually even make a copy according to most people's definition (yes, a bad idea, but it still might happen)) it still might be eliminated and the funky non-copy thing not happen? – Omnifarious Feb 21 '11 at 09:55
  • 1
    @Omnifarious: I believe so: pretty sure I read somewhere (albeit many years ago) that this was one case where the Standards Committee had felt it reasonable to expect the equivalence... the common optimisation benefits outweighting the potential surprise factor in what would be largely counterintuitive and error-prone code anyway.... – Tony Delroy Feb 21 '11 at 10:00
  • That's correct. The "permitted to eliminate" is normative wording. – MSalters Feb 21 '11 at 10:01
  • 1
    @Tony - I just tested it assuming that gcc 4.5.1 is standards compliant in this way, and yes, you are correct, the copy is eliminated and the funky non-copy thing does not happen. – Omnifarious Feb 21 '11 at 10:02
  • This also means that the declaration `T a = x;` is a special form that does not ever invoke the assignment operator. – Omnifarious Feb 21 '11 at 10:14
  • @Omnifarious: reading the Standard, I've never been able to satisfy myself that I could *always* expect this behaviour. Even the boldfaced phrase above begins with "In certain cases". Would be happy to be told otherwise.... – Tony Delroy Feb 21 '11 at 10:22
  • 1
    I'd like to add a thought for the future: C++0x has move-semantics, which can, if properly used (and the STL will do so), make copies from temporaries so efficient that the difference, even if a real copy would occur, will be neglectible. For example, `std::vector foo = std::vector(10000, 42)` would, if copied and not initialized into `foo`, just swap the internal pointers and sizes, leaving the temporary empty and eliminating the whole copy and destruction of the actual array. Of course, user-defined class types would have to implement a proper move-constructor. – Mephane Feb 21 '11 at 10:56
  • There are multiple good answers here. I like this one the best, but it would be even better if it incorporated elements of the other two good answers. :-) I may edit it myself if you don't. – Omnifarious Feb 21 '11 at 17:43
  • @Omnifarious: just seen your request, and read over the other answers again. Seems to me like Maxim's answer's pretty much a summation anyway...? (You're welcome to edit this if you prefer). – Tony Delroy Feb 22 '11 at 01:25
2

The difference here is between implicit and explicit construction, and there can be difference.

Imagine having a type Array with the constructor Array(size_t length), and that somewhere else, you have a function count_elements(const Array& array). The purpose of these are easily understandable, and the code seems readable enough, until you realise it will allow you to call count_elements(2000). This is not only ugly code, but will also allocate an array 2000 elements long in memory for no reason.

In addition, you may have other types that are implicitly castable to an integer, allowing you to run count_elements() on those too, giving you completely useless results at a high cost to efficiency.

What you want to do here, is declare the Array(size_t length) an explicit constructor. This will disable the implicit conversions, and Array a = 2000 will no longer be legal syntax.

This was only one example. Once you realise what the explicit keyword does, it is easy to dream up others.

Pianosaurus
  • 5,538
  • 2
  • 20
  • 15
  • _In addition, you may have other types that are implicitly castable to an integer, allowing you to run count_elements() on those too, giving you completely useless results at a high cost to efficiency._ Though, if I understand other parts of the standard correctly, the conversion to integer will have to be a non-user-defined conversion since only one user-defined conversion is allowed in any conversion chain. – Omnifarious Feb 21 '11 at 10:07
  • +1 Bringing implicit/explicit into the analysis can also move the issue to `T t(x)` vs `T t = T(x)`.... – Tony Delroy Feb 21 '11 at 10:14
  • @Omnifarious: Could you link a source to what you're saying? If this is true, that could be very interesting for solving the "safe bool idiom" with an invisible intermediate class. – Mephane Feb 21 '11 at 10:48
  • @Mephane: Here is an example of what I'm talking about: http://paste.lisp.org/+2KI0/1 – Omnifarious Feb 21 '11 at 17:39
  • By source I meant something like a part of the C++ standard explicitly stating this, not an example made by yourself; especially since your piece of code does compile, as I would have expected. – Mephane Feb 22 '11 at 13:58
2

T a(x) is direct initialization and T a = x is copy initialization.

From the standard:

8.5.11 The form of initialization (using parentheses or =) is generally insignificant, but does matter when 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.

8.5.12 The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form

   T x = a;

The initialization that occurs in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization and is equivalent to the form

    T x(a);

The difference is that copy initialization creates a temporary object which is then used to direct-initialize. The compiler is allowed to avoid creating the temporary object:

8.5.14 ... The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.

Copy initialization requires a non-explicit constructor and a copy constructor to be available.

Community
  • 1
  • 1
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
0

In C++, when you write this:

class A {
  public:
  A() { ... }
};

The compiler actually generates this, depending on what your code uses:

class A {
  public:
  A() { ... }
  ~A() { ... }
  A(const A& other) {...}
  A& operator=(const A& other) { ... }
};

So now you can see the different semantics of the various constructors.

A a1; // default constructor
A a2(a1); // copy constructor
a2 = a1; // copy assignment operator

The copy constructors basically copy all the non-static data. They are only generated if the resulting code is legal and sane: if the compiler sees types inside the class that he doesn't know how to copy (per normal assignment rules), then the copy constructor won't get generated. This means that if the T type doesn't support constructors, or if one of the public fields of the class is const or a reference type, for instance, the generator won't create them - and the code won't build. Templates are expanded at build time, so if the resulting code isn't buildable, it'll fail. And sometimes it fails loudly and very cryptically.

If you define a constructor (or destructor) in a class, the generator won't generate a default one. This means you can override the default generated constructors. You can make them private (they're public by default), you can override them so they do nothing (useful for saving memory and avoiding side-effects), etc.

Andreia Gaita
  • 518
  • 3
  • 6