8

I was reading the difference between direct-initialization and copy-initialization (§8.5/12):

T x(a);  //direct-initialization
T y = a; //copy-initialization

What I understand from reading about copy-initialization is that it needs accessible & non-explicit copy-constructor, or else the program wouldn't compile. I verified it by writing the following code:

struct A
{
   int i;
       A(int i) : i(i) { std::cout << " A(int i)" << std::endl; }
   private:
       A(const A &a)  {  std::cout << " A(const A &)" << std::endl; }
};

int main() {
        A a = 10; //error - copy-ctor is private!
}

GCC gives an error (ideone) saying:

prog.cpp:8: error: ‘A::A(const A&)’ is private

So far everything is fine, reaffirming what Herb Sutter says,

Copy initialization means the object is initialized using the copy constructor, after first calling a user-defined conversion if necessary, and is equivalent to the form "T t = u;":


After that I made the copy-ctor accessible by commenting the private keyword. Now, naturally I would expect the following to get printed:

A(const A&)

But to my surprise, it prints this instead (ideone):

A(int i)

Why?

Alright, I understand that first a temporary object of type A is created out of 10 which is int type, by using A(int i), applying the conversion rule as its needed here (§8.5/14), and then it was supposed to call copy-ctor to initialize a. But it didn't. Why?

If an implementation is permitted to eliminate the need to call copy-constructor (§8.5/14), then why is it not accepting the code when the copy-constructor is declared private? After all, its not calling it. Its like a spoiled kid who first irritatingly asks for a specific toy, and when you give him one, the specific one, he throws it away, behind your back. :|

Could this behavior be dangerous? I mean, I might do some other useful thing in the copy-ctor, but if it doesn't call it, then does it not alter the behavior of the program?

Community
  • 1
  • 1
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • see my question here http://stackoverflow.com/questions/4733448/why-copy-constructor-is-called-when-passing-temp-by-const-ref, I believe this is a similar case – davka May 28 '11 at 17:08
  • BTW "What I understand from reading about copy-initialization is that it needs accessible & non-explicit copy-constructor, or else the program wouldn't compile." - it is more intricate than that. In your example if you make your copy-ctor public but *explicit*, it will still work. See http://llvm.org/bugs/show_bug.cgi?id=8342 – Johannes Schaub - litb May 28 '11 at 21:40
  • @Johannes: GCC doesn't accept that as well : http://www.ideone.com/KiOTV – Nawaz May 28 '11 at 22:04
  • @Nawaz: very good question !!! – Destructor Sep 24 '15 at 04:56

6 Answers6

10

Are you asking why the compiler does the access check? 12.8/14 in C++03:

A program is ill-formed if the copy constructor or the copy assignment operator for an object is implicitly used and the special member function is not accessible

When the implementation "omits the copy construction" (permitted by 12.8/15), I don't believe this means that the copy ctor is no longer "implicitly used", it just isn't executed.

Or are you asking why the standard says that? If copy elision were an exception to this rule about the access check, your program would be well-formed in implementations that successfully perform the elision, but ill-formed in implementations that don't.

I'm pretty sure the authors would consider this a Bad Thing. Certainly it's easier to write portable code this way -- the compiler tells you if you write code that attempts to copy a non-copyable object, even if the copy happens to be elided in your implementation. I suspect that it could also inconvenience implementers to figure out whether the optimization will be successful before checking access (or to defer the access check until after the optimization is attempted), although I have no idea whether that warranted consideration.

Could this behavior be dangerous? I mean, I might do some other useful thing in the copy-ctor, but if it doesn't call it, then does it not alter the behavior of the program?

Of course it could be dangerous - side-effects in copy constructors occur if and only if the object is actually copied, and you should design them accordingly: the standard says copies can be elided, so don't put code in a copy constructor unless you're happy for it to be elided under the conditions defined in 12.8/15:

MyObject(const MyObject &other) {
    std::cout << "copy " << (void*)(&other) << " to " << (void*)this << "\n"; // OK
    std::cout << "object returned from function\n"; // dangerous: if the copy is
      // elided then an object will be returned but you won't see the message.
}
Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • Steve, can you explain your code-snippet a bit more? I didn't really understand it, and when you said `you should design them accordingly`. How exactly? – Nawaz May 28 '11 at 18:04
  • 1
    +1 for attempting to answer my question `"Could this behavior be dangerous?"`. – Nawaz May 28 '11 at 18:05
  • 1
    Isn't the basic question here about accessibility? –  May 28 '11 at 18:05
  • @Neil: Actually there are several questions. And it doesn't matter to me if you call one question *basic*, and others *non-basic*. Because I wouldn't be completely satisfied if any of my questions remains unanswered. :|...I'm equally curious about all questions...:D – Nawaz May 28 '11 at 18:08
  • @Neil: yes, but I interpreted the "if it doesn't call it, does it not alter the behaviour of the program" part to be about copy ctor elision in general, not just about the need for the accessibility check, and I think the questioner backs me on that by upvoting :-) I don't think the accessibility check is dangerous. – Steve Jessop May 28 '11 at 18:09
  • @Steve: You're right, Steve. In fact, I wanted to write : this question can be asked in a more general sense, pointing out other situations, when copy-elision is performed by the compiler. But I didnt wanted to make the post long, boring and repetitive. – Nawaz May 28 '11 at 18:11
  • 2
    @Nawaz: by "design accordingly", I basically mean, "know the standard, and don't rely on code being executed if the standard says it might not be executed". In my code-snippet, the first line says that copying occurs, and it is printed if copying occurs. The second line says that object-return occurs, but it might not be printed even when object-return occurs, so if you rely on it to appear then you'll misinterpret the output of the program. – Steve Jessop May 28 '11 at 18:13
  • 1
    There's a weakness in the example, though - the second line can occur even when object-return isn't happening, specifically when the object is copied for any other reason. I hope that isn't confusing the issue, if so then just imagine that somehow I already knew, before adding that line, that in my program there are no copies except (possibly) copies that occur in relation to object-return. – Steve Jessop May 28 '11 at 18:15
5

C++ explicitly allows several optimizations involving the copy constructor that actually change the semantics of the program. (This is in contrast with most optimizations, which do not affect the semantics of the program). In particular, there are several cases where the compiler is allowed to re-use an existing object, rather than copying one, if it knows that the existing object will become unreachable. This (copy construction) is one such case; another similar case is the "return value optimization" (RVO), where if you declare the variable that holds the return value of a function, then C++ can choose to allocate that on the frame of the caller, so that it doesn't need to copy it back to the caller when the function completes.

In general, in C++, you are playing with fire if you define a copy constructor that has side effects or does anything other than just copying.

Edward Loper
  • 15,374
  • 7
  • 43
  • 52
  • +1. Nice post. Particularly this : *there are several cases where the compiler is allowed to re-use an existing object, rather than copying one, **if it knows that the existing object will become unreachable**. This (copy construction) is one such case; another similar case is the "return value optimization" (RVO), **where if you declare the variable that holds the return value of a function, then C++ can choose to allocate that on the frame of the caller**, so that it doesn't need to copy it back to the caller when the function completes* – Nawaz May 28 '11 at 18:33
4

In any compiler, syntax [and semantic] analysis process are done prior to the code optimization process.

The code must be syntactically valid otherwise it won't even compile. Its only in the later phase (i.e code optimization) that the compiler decides to elide the temporary that it creates.

So you need an accessible copy c-tor.

Prasoon Saurav
  • 91,295
  • 49
  • 239
  • 345
  • 1
    That is very much implied, Prasoon, that syntax analysis is done before the optimization phase, that is why it needs the copy-ctor publicly declared. My question is, why is it done so? – Nawaz May 28 '11 at 17:25
  • @Nawaz : "If an implementation is permitted to eliminate the need to call copy-constructor (§8.5/14), then why is it not accepting the code when the copy-constructor is declared private? " Because your code with a private copy constructor is ill-formed. – Prasoon Saurav May 28 '11 at 17:25
  • 1
    But at the semantic analysis time, C++ compilers are specifically allowed to ellide copy ctors under most circumstances. This is not an optimisation, in the common sense. –  May 28 '11 at 17:29
  • @Nawaz : How? The compiler is allowed to do any kind of optimization that it wants to do. You should not be surprised. – Prasoon Saurav May 28 '11 at 17:30
  • 2
    @Prasoon It isn't allowed to do that. Specifically, it isn't allowed not to construct objects under certain circumstances, otherwise the whole RAII thing would fall apart. –  May 28 '11 at 17:31
  • 1
    Perhaps it would be a good point to point out "elide" != "optimise". Really, NRO should be NRE. –  May 28 '11 at 17:36
  • @Prasoon: deleted my comments, since you edited yours, and my comment appear to be something different than I *actually* meant. – Nawaz May 28 '11 at 17:36
  • @Nawaz : I didn't edit any comment [except the first one]. :) – Prasoon Saurav May 28 '11 at 17:41
  • @Neil: Your comment about RAII seems interesting. Can you elaborate a bit on this? – Nawaz May 28 '11 at 17:41
  • @Prasoon: `I didn't edit any comment [except the first one].`... contradiction :P – Nawaz May 28 '11 at 17:41
  • @Nawaz One must be able to create a (for example) RAII lock object, even if that object is never used it cannot be optimised away. This is actually spelt out in the standard, but I'm not in trawling mood at the moment. –  May 28 '11 at 17:45
  • 2
    @Neil: that's just the "as-if" rule, isn't it? That's the only thing that allows an "unused" object to be optimized away, but if creating and destroying it has observable side-effects then that optimization isn't allowed. And any sensible implementation defines lock manipulation to be observable, even if it doesn't explicitly modify a `volatile` object. – Steve Jessop May 28 '11 at 18:05
1

Here you can find this (with your comment ;)):

[the standard] also says that the temporary copy can be elided, but the semantic constraints (eg. accessibility) of the copy constructor still have to be checked.

Community
  • 1
  • 1
davka
  • 13,974
  • 11
  • 61
  • 86
0

RVO and NRVO, buddy. Perfectly good case of copy ellision.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • Except his copy constructor is private - I'm not sure if ellision is allowed. –  May 28 '11 at 17:13
  • It doesn't answer my question. And I know about RVO and NRVO. – Nawaz May 28 '11 at 17:17
  • Actually, thinking about it, it almost certainly is - I don't see why privacy should make any difference. But I quail before finding a standard reference for this :-) –  May 28 '11 at 17:19
0

This is an optimization by the compiler.

In evaluating: A a = 10; instead of:

  1. first constructing a temporary object through A(int);

  2. constructing a through the copy constructor and passing in the temporary;

the compiler will simply construct a using A(int).

sergio
  • 68,819
  • 11
  • 102
  • 123