C ++ unexpected behavior (where are my temporary !?)

It was an r-value experiment, but it mutated when gcc whined me about the lack of a move constructor (I deleted it) and did not return to the copy constructor (as I expected) Then I removed -std = C ++ 11 from the flags and tried what you see below, it has a lot of output (initially it wasn’t), because I'm trying to understand why this is not working (I know how to debug, but I find messages on stdout a good indicator of what is happening)

Here is my code:

#include <iostream> class Object { public: Object() { id=nextId; std::cout << "Creating object: "<<id<<"\n"; nextId++; } Object(const Object& from) { id=nextId; std::cout << "Creating object: "<<id<<"\n"; nextId++; std::cout<<"(Object: "<<id<<" created from Object: "<<from.id<<")\n"; } Object& operator=(const Object& from) { std::cout<<"Assigning to "<<id<<" from "<<from.id<<"\n"; return *this; } ~Object() { std::cout<<"Deconstructing object: "<<id<<"\n";} private: static int nextId; int id; }; int Object::nextId = 0; Object test(); int main(int,char**) { Object a; std::cout<<"A ought to exist\n"; Object b(test()); std::cout<<"B ought to exist\n"; Object c = test(); std::cout<<"C ought to exist\n"; return 0; } Object test() { std::cout<<"In test\n"; Object tmp; std::cout<<"Test tmp ought to exist\n"; return tmp; } 

Output:

 Creating object: 0 A ought to exist In test Creating object: 1 Test tmp ought to exist B ought to exist In test Creating object: 2 Test tmp ought to exist C ought to exist Deconstructing object: 2 Deconstructing object: 1 Deconstructing object: 0 

I use deconstruction because deconstruction is already a word, sometimes I use a destructor, I am never happy with a word, I stand for a destructor as a noun.

Here is what I expected:

 A to be constructed tmp in test to be constructed, a temporary to be created from that tmp, tmp to be destructed(?) that temporary to be the argument to B copy constructor the temporary to be destructed. C default constructor to be used "" with a temporary from `test` C assignment operator to be used the temporary to be destructed c,b,a to be destructed. 

I was called "die-hard C", and I'm trying to learn how to use C ++ as something more than "C with namespaces."

Some might say that "the compiler optimizes it." I would like this person to never answer a question with such an answer now or ever, optimization should not change the state of the program, it should all happen as the specification says, so the compiler can laugh at me by placing a message on cout, which includes a number, he may not even bother to increase the number, etc., but the output of the program will be the same as if he did everything that describes the code.

So this is not optimization, what's going on?

+4
source share
4 answers

This is the only optimization that allows you to change the observed behavior of the program.

Here is the paragraph 12.8./31 , taken from the standard draft n3337 (my emphasis):

When certain criteria are met, implementations are allowed to omit the copy / move construct of the object class, even if the copy / move constructor and / or destructor for the object have side effects. In such cases, the implementation considers the source and purpose of the missed copy / move operation as just two different ways of accessing the same object, and the destruction of this object occurs in later times when two objects would be destroyed without optimization. This permission to copy / move operations, called a copy, is permitted in the following circumstances (which can be combined with eliminate multiple copies):

- in the return statement in a function with a return type of the class, when the expression is a non-volatile automatic object name (except for a function or catch-clause parameter) with the same cv- unqualified type as the return type of the function, the copy / move operation can be omitted when building the automatic the object directly in the function returns a value

- in the throw expression, when the operand is the name of a non-volatile automatic object (except for a function or catch-clause parameter), the volume of which does not go beyond the inner (if any), the copy / move operation from the operand to the exception object (15.1) can be omitted, building an automatic object directly into an exception object

- if the temporary object of the class that was not attached to the link (12.2) is copied / moved to the class object with the same cv-unqualified type, the copy / move operation can be omitted from building the temporary object directly to the target of the missed copy / move

- when an exception handler exception declaration (section 15) declares an object of the same type (except for cv-quali fiation) as an exception object (15.1), the copy / move operation can be omitted by processing the exception declaration as an alias for the exception object, if the program value will not be changed, except for the execution of constructors and destructors for the object declared exception declaration.

[Example ... omitted]

The semantics of the copy / move constructor are simply copying / moving the contents of an object when initializing another. If your copy designers send invitations to your birthday, you shouldn't be surprised if you finish the party alone :)

OK, some copy constructors do other things too. Think of smart pointer reference counting. But if it is optimized, everything is in order. There was no copy, and nothing had to be calculated.

+5
source

I believe that you are experiencing Copy Elision. And thus, yes, it is an optimization.

http://en.wikipedia.org/wiki/Copy_elision

In C ++ computer programming, copy elision refers to compiler optimization, which eliminates unnecessary copying of objects. The C ++ language standard usually allows implementations to perform any optimization, provided that as a result the observed behavior of the program is the same as if, for example, pretending to be, the program was executed exactly as provided by the standard.

The standard also describes several situations where copying can be excluded , even if this changes the behavior of the program , the most common is to optimize the return value.

The emphasis is mine.

+3
source

Since copying / moving temporary objects is costly, compilers are explicitly allowed to exclude temporary objects, even if the corresponding constructors or destructors have side effects. Copying / moving elision is usually not considered an optimization, and most compilers allow you to create temporary objects even in debug mode (which is reasonable since you do not want to have different behavior between debug and optimized assemblies).

The corresponding proposal in the C ++ 11 standard is 12.8 [class.copy], paragraph 31:

When certain implementation criteria are met, it is allowed to omit the copy / move construction of the class object, even if the constructor selected for the copy / move operation and / or the object's destructor has side effects ....

Allowed cases when copying / moving is allowed:

  • in return statements
  • in throw expressions
  • when temporary is not attached to the link
  • in catch clause

Exact rules have a few additional conditions.

+2
source

To prevent copying, perform the assignment operation using the copy / replace algorithm as follows:

 Object &operator =(Object other) { std::swap(*this, other); return *this; } 

And then try:

 Object a; a = test(); 

Thus, a copy (or move) ctor will be called by the compiler when passing the object to the assignment operator.

0
source

Source: https://habr.com/ru/post/1499361/


All Articles