简体   繁体   中英

is twice calls to copy constructor happen for this c++ code sample?

for method:

Object test(){
    Object str("123");
    return str;
}

then, I had two methods to call it:

code 1:

const Object &object=test();

code 2:

Object object=test();

which one is better? is twice calls to copy constructor happen in code 2 if without optimize?

other what's the difference?

for code2 I suppose:

Object tmp=test();
Object object=tmp;

for code1 I suppose:

Object tmp=test();
Object &object=tmp;

but the tmp will be deconstructor after the method.so it must add const?

is code 1 right without any issues?

In C++11, std::string has a move constructor / move assignment operator, hence the code:

string str = test();

will (at worst) have one constructor call and one move assignment call.

Even without move semantics, this will (likely) be optimised away by NRVO (return value optimisation).

Don't be afraid of returning by value, basically.

Edit: Just to make it 100% clear what is going on:

#include <iostream>
#include <string>

class object
{
    std::string s;

public:

    object(const char* c) 
      : s(c)
    {
        std::cout << "Constructor\n";
    }

    ~object() 
    {
        std::cout << "Destructor\n";
    }

    object(const object& rhs)
      : s(rhs.s)
    {
        std::cout << "Copy Constructor\n";
    }

    object& operator=(const object& rhs)
    {
        std::cout << "Copy Assignment\n";
        s = rhs.s;
        return *this;
    }

    object& operator=(object&& rhs)
    {
        std::cout << "Move Assignment\n";
        s = std::move(rhs.s);
        return *this;
    }

    object(object&& rhs)
      : s(std::move(rhs.s))
    {
        std::cout << "Move Constructor\n";
    }
};

object test()
{
    object o("123");
    return o;
}

int main()
{
    object o = test();
    //const object& o = test();
}

You can see that there is 1 constructor call and 1 destructor call for each - NRVO kicks in here (as expected) eliding the copy/move.

Both your examples are valid - in 1 const reference refers to a temporary object, but lifetime of this object is prolonged till the reference goes out of scope (see http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/ ). The second example is obviously valid, and most modern compilers will optimize away additional copying (even better if you use C+11 move semantics) so for practical purposes examples are equivalent (though in 2 additionally you can modify the value).

Let's analyse your function:

 Object test() { Object temp("123"); return temp; } 

Here you're constructing a local variable named temp and returning it from the function. The return type of test() is Object meaning you're returning by value. Returning local variables by value is a good thing because it allows a special optimization technique called Return Value Optimization (RVO) to take place. What happens is that instead of invoking a call to the copy or move constructor, the compiler will elide that call and directly construct the initializer into the address of the caller. In this case, because temp has a name (is an lvalue), we call it N(amed)RVO.

Assuming optimizations take place, no copy or move has been performed yet. This is how you would call the function from main :

 int main() { Object obj = test(); } 

That first line in main seems to be of particular concern to you because you believe that the temporary will be destroyed by the end of the full expression. I'm assuming it is a cause for concern because you believe obj will not be assigned to a valid object and that initializing it with a reference to const is a way to keep it alive.

You are right about two things:

  • The temporary will be destroyed at the end of the full expression
  • Initializing it with a reference to const will extend its life time

But the fact that the temporary will be destroyed is not a cause for concern. Because the initializer is an rvalue, its contents can be moved from.

 Object obj = test(); // move is allowed here 

Factoring in copy-elision, the compiler will elide the call to the copy or move constructor. Therefore, obj will be initialized "as if" the copy or move constructor was called. So because of these compiler optimizations, we have very little reason to fear multiple copies.


But what if we entertain your other examples? What if instead we had qualified obj as:

 Object const& obj = test(); 

test() returns a prvalue of type Object . This prvalue would normally be destructed at the end of the full expression in which it is contained, but because it is being initialized to a reference to const , its lifetime is extended to that of the reference.

What are the differences between this example and the previous one?:

  • You cannot modify the state of obj
  • It inhibits move semantics

The first bullet point is obvious but not the second if you are unfamiliar with move semantics. Because obj is a reference to const , it cannot be moved from and the compiler cannot take advantage of useful optimizations. Assigning reference to const to an rvalue is only helpful in a narrow set of circumstances (as DaBrain has pointed out). It is instead preferable that you exercise value-semantics and create value-typed objects when it makes sense.

Moreover, you don't even need the function test() , you can simply create the object:

 Object obj("123"); 

but if you do need test() , you can take advantage of type deduction and use auto :

 auto obj = test(); 

Your last example deals with an lvalue-reference:

[..] but the tmp will be destructed after the method. So must we add const ?

 Object &object = tmp; 

The destructor of tmp is not called after the method. Taking in to account what I said above, the temporary to which tmp is being initialized will be moved into tmp (or it will be elided). tmp itself doesn't destruct until it goes out of scope. So no, there is no need to use const .

But a reference is good if you want to refer to tmp through some other variable. Otherwise, if you know you will not need tmp afterwards, you can move from it:

 Object object = std::move(tmp); 

Code 1 is correct. As I said, the C++ Standard guarantees a temporary to a const reference is valid. It's main usage is polymorphic behavior with refenences:

#include <iostream>

class Base { public: virtual void Do() const { std::cout << "Base"; } };
class Derived : public Base { public: virtual void Do() const { std::cout << "Derived"; } };

Derived Factory() { return Derived(); }

int main(int argc, char **argv)
{
    const Base &ref = Factory();

    ref.Do();

    return 0;
}

This will return "Derived". A famouse example was Andrei Alexandrescu's ScopeGuard but with C++11 it's even simpler yet.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM