0

Given the following code

#include <iostream>

class Foo {
  public:
    ~Foo();
    Foo(const Foo& that);
    static Foo create();
  private:
    Foo();
};

Foo::Foo() {
    std::cout << "ctor\n";
}

Foo::~Foo() {
    std::cout << "dstor\n";
}

Foo::Foo(const Foo& /* that */) {
    std::cout << "copy\n";
}

Foo Foo::create() {
    Foo temp;
    return temp;
}

int main() {
    Foo f = Foo::create();
    return 0;
}

In particular the line Foo f = Foo::create(), was there ever a time in C++ history where multiple objects would be created/destroyed? As it is, if I run it I just get

ctor
dstor

So only one object.

In my mind temp and f are different objects, temp is created in the scope of Foo::create which means it has to be destructed inside Foo::create(). A separate object f is created in main.

I get that C++ 11 added move semantics. Before C++ 11 what used to happen?

As another example

#include <iostream>

class Foo {
  public:
    ~Foo();
    Foo(const Foo& that);
    static Foo create(int v);
    void show() const;
  private:
    Foo(int v);
    int v;
    int* p;
};

Foo::Foo(int _v) : v(_v) {
    p = &v;                   // <-----------------
    std::cout << "ctor\n";
}

Foo::~Foo() {
    std::cout << "dstor\n";
}

Foo::Foo(const Foo& /*that*/) {
    std::cout << "copy\n";
}

void Foo::show() const {
    std::cout << *p << "\n";
}

Foo Foo::create(int v) {
    Foo temp(v);
    return temp;
}

int main() {
    Foo f = Foo::create(123);
    f.show();
    return 0;
}

Note the line above. p is a pointer to v. So, now, if temp exists on the stack only in the scope of create then if it's copied or moved p will be wrong. The copy constructor is never called, at least in my tests. If I add a move constructor, it's never called either.

I can nest the call to create in other function calls

Foo f2(int v) {
    int a = v * 2;
    Foo t = Foo::create(a);
    return t;
}

Foo f1(int v) {
    int a = v * 2;
    Foo t = f2(a);
    return t;
}

int main() {
    Foo f = f1(123);
    f.show();
}

I'm missing how t in f2, created in f2s scope is making it back down to main neither being copied nor moved.

samanthaj
  • 521
  • 3
  • 14
  • 2
    [Copy Elision](https://en.cppreference.com/w/cpp/language/copy_elision) is what we counted on before (and since) move operations. – user4581301 Jun 10 '22 at 17:15
  • 2
    Copy elision was allowed since as far as C++ existed, if memory serves. – StoryTeller - Unslander Monica Jun 10 '22 at 17:16
  • Who ever closed the question as a dupe wasn't paying attention. The other question is tagged C++17. This one is about older C++ – samanthaj Jun 10 '22 at 17:22
  • 1
    If you are old enough, the compiler wars between Borland, Watcom, Microsoft, and others hinged on the fact that these compilers tried to outdo each other on performance, mostly due to copy elision, even though it was not officially called that at that time. – PaulMcKenzie Jun 10 '22 at 17:24
  • 1
    @samanthaj Read both the duplicates the explanation is given there for both C++17 and Prior C++17. – Jason Jun 10 '22 at 17:31
  • 1
    I remember Howard Hinnant saying in a presentation that move semantics, and all the infrastructure that is required for it, was to make his `std::vector` implementation faster. And because I'm not a dick who just appeals to higher authority without trying to back it up, [I looked up the presentation](https://www.youtube.com/watch?v=vLinb2fgkHk). Worth the time spent watching it. – user4581301 Jun 10 '22 at 17:56

1 Answers1

1

In particular the line Foo f = Foo::create(), was there ever a time in C++ history where multiple objects would be created/destroyed?

Yes, that's the present. As of current C++20 this creates either one or two objects, which of the two is up to the compiler. The variant with only one object being created (where f and temp are considered the same object) is called copy elision/named return value optimization.

Before C++17, the line could create up to three different objects, one of them an unnamed temporary for the return value of the function. Also note that if your class had trivial copy/move constructors and destructor, this would still be true with current C++ as well.

But in either case the variant creating only one object (by performing return value optimization and named return value optimization to elide the two intermediate objects) has been allowed, but not required, since C++98. I don't know to what degree any such copy elision was done in pre-ISO C++ implementations.

I get that C++ 11 added move semantics. Before C++ 11 what used to happen?

C++11 move semantics don't change anything about the number of objects created. It only changes how the additional objects in the chain are constructed (if they are not elided). With C++11 move semantics, the compiler will use the move constructor instead of the copy constructor if possible, which may be cheaper (for example std::string is much cheaper to move-construct than copy-construct). But in your example there is no move constructor anyway.


Looking through the publicly available old ISO papers/proposals, I found N0641 from 1995 proposing to add copy elision to the language standard in a much broader scope, applying not only to return statements, but any case where a copy of an object which is not referred to again is made. According to the paper (more limited forms of) the idea had already been several years old at that point and had been widely-implemented.

The later N1103 from 1997 has the much more limited scope which is present in C++98 and later (without explanation).

user17732522
  • 53,019
  • 2
  • 56
  • 105