Advantages of pass-by-value and std::move over pass-by-reference

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
45
down vote

favorite
16












I'm learning C++ at the moment and try avoid picking up bad habits.
From what I understand, clang-tidy contains many "best practices" and I try to stick to them as best as possible (even though I don't necessarily understand why they are considered good yet), but I'm not sure if I understand what's recommended here.



I used this class from the tutorial:



class Creature

private:
std::string m_name;

public:
Creature(const std::string &name)
: m_namename


;


This leads to a suggestion from clang-tidy that I should pass by value instead of reference and use std::move.
If I do, I get the suggestion to make name a reference (to ensure it does not get copied every time) and the warning that std::move won't have any effect because name is a const so I should remove it.



The only way I don't get a warning is by removing const altogether:



Creature(std::string name)
: m_namestd::move(name)




Which seems logical, as the only benefit of const was to prevent messing with the original string (which doesn't happen because I passed by value).
But I read on CPlusPlus.com:




Although note that -in the standard library- moving implies that the moved-from object is left in a valid but unspecified state. Which means that, after such an operation, the value of the moved-from object should only be destroyed or assigned a new value; accessing it otherwise yields an unspecified value.




Now imagine this code:



std::string nameString("Alex");
Creature c(nameString);


Because nameString gets passed by value, std::move will only invalidate name inside the constructor and not touch the original string. But what are the advantages of this? It seems like the content gets copied only once anyhow - if I pass by reference when I call m_namename, if I pass by value when I pass it (and then it gets moved). I understand that this is better than passing by value and not using std::move (because it gets copied twice).



So two questions:



  1. Did I understand correctly what is happening here?

  2. Is there any upside of using std::move over passing by reference and just calling m_namename?






share|improve this question



















  • With pass by reference, Creature c("John"); makes an extra copy
    – immibis
    Aug 7 at 5:46






  • 1




    This link might be a valuable read, it covers passing std::string_view and SSO, too.
    – lubgr
    Aug 7 at 7:34














up vote
45
down vote

favorite
16












I'm learning C++ at the moment and try avoid picking up bad habits.
From what I understand, clang-tidy contains many "best practices" and I try to stick to them as best as possible (even though I don't necessarily understand why they are considered good yet), but I'm not sure if I understand what's recommended here.



I used this class from the tutorial:



class Creature

private:
std::string m_name;

public:
Creature(const std::string &name)
: m_namename


;


This leads to a suggestion from clang-tidy that I should pass by value instead of reference and use std::move.
If I do, I get the suggestion to make name a reference (to ensure it does not get copied every time) and the warning that std::move won't have any effect because name is a const so I should remove it.



The only way I don't get a warning is by removing const altogether:



Creature(std::string name)
: m_namestd::move(name)




Which seems logical, as the only benefit of const was to prevent messing with the original string (which doesn't happen because I passed by value).
But I read on CPlusPlus.com:




Although note that -in the standard library- moving implies that the moved-from object is left in a valid but unspecified state. Which means that, after such an operation, the value of the moved-from object should only be destroyed or assigned a new value; accessing it otherwise yields an unspecified value.




Now imagine this code:



std::string nameString("Alex");
Creature c(nameString);


Because nameString gets passed by value, std::move will only invalidate name inside the constructor and not touch the original string. But what are the advantages of this? It seems like the content gets copied only once anyhow - if I pass by reference when I call m_namename, if I pass by value when I pass it (and then it gets moved). I understand that this is better than passing by value and not using std::move (because it gets copied twice).



So two questions:



  1. Did I understand correctly what is happening here?

  2. Is there any upside of using std::move over passing by reference and just calling m_namename?






share|improve this question



















  • With pass by reference, Creature c("John"); makes an extra copy
    – immibis
    Aug 7 at 5:46






  • 1




    This link might be a valuable read, it covers passing std::string_view and SSO, too.
    – lubgr
    Aug 7 at 7:34












up vote
45
down vote

favorite
16









up vote
45
down vote

favorite
16






16





I'm learning C++ at the moment and try avoid picking up bad habits.
From what I understand, clang-tidy contains many "best practices" and I try to stick to them as best as possible (even though I don't necessarily understand why they are considered good yet), but I'm not sure if I understand what's recommended here.



I used this class from the tutorial:



class Creature

private:
std::string m_name;

public:
Creature(const std::string &name)
: m_namename


;


This leads to a suggestion from clang-tidy that I should pass by value instead of reference and use std::move.
If I do, I get the suggestion to make name a reference (to ensure it does not get copied every time) and the warning that std::move won't have any effect because name is a const so I should remove it.



The only way I don't get a warning is by removing const altogether:



Creature(std::string name)
: m_namestd::move(name)




Which seems logical, as the only benefit of const was to prevent messing with the original string (which doesn't happen because I passed by value).
But I read on CPlusPlus.com:




Although note that -in the standard library- moving implies that the moved-from object is left in a valid but unspecified state. Which means that, after such an operation, the value of the moved-from object should only be destroyed or assigned a new value; accessing it otherwise yields an unspecified value.




Now imagine this code:



std::string nameString("Alex");
Creature c(nameString);


Because nameString gets passed by value, std::move will only invalidate name inside the constructor and not touch the original string. But what are the advantages of this? It seems like the content gets copied only once anyhow - if I pass by reference when I call m_namename, if I pass by value when I pass it (and then it gets moved). I understand that this is better than passing by value and not using std::move (because it gets copied twice).



So two questions:



  1. Did I understand correctly what is happening here?

  2. Is there any upside of using std::move over passing by reference and just calling m_namename?






share|improve this question











I'm learning C++ at the moment and try avoid picking up bad habits.
From what I understand, clang-tidy contains many "best practices" and I try to stick to them as best as possible (even though I don't necessarily understand why they are considered good yet), but I'm not sure if I understand what's recommended here.



I used this class from the tutorial:



class Creature

private:
std::string m_name;

public:
Creature(const std::string &name)
: m_namename


;


This leads to a suggestion from clang-tidy that I should pass by value instead of reference and use std::move.
If I do, I get the suggestion to make name a reference (to ensure it does not get copied every time) and the warning that std::move won't have any effect because name is a const so I should remove it.



The only way I don't get a warning is by removing const altogether:



Creature(std::string name)
: m_namestd::move(name)




Which seems logical, as the only benefit of const was to prevent messing with the original string (which doesn't happen because I passed by value).
But I read on CPlusPlus.com:




Although note that -in the standard library- moving implies that the moved-from object is left in a valid but unspecified state. Which means that, after such an operation, the value of the moved-from object should only be destroyed or assigned a new value; accessing it otherwise yields an unspecified value.




Now imagine this code:



std::string nameString("Alex");
Creature c(nameString);


Because nameString gets passed by value, std::move will only invalidate name inside the constructor and not touch the original string. But what are the advantages of this? It seems like the content gets copied only once anyhow - if I pass by reference when I call m_namename, if I pass by value when I pass it (and then it gets moved). I understand that this is better than passing by value and not using std::move (because it gets copied twice).



So two questions:



  1. Did I understand correctly what is happening here?

  2. Is there any upside of using std::move over passing by reference and just calling m_namename?








share|improve this question










share|improve this question




share|improve this question









asked Aug 6 at 10:53









Blackbot

22726




22726











  • With pass by reference, Creature c("John"); makes an extra copy
    – immibis
    Aug 7 at 5:46






  • 1




    This link might be a valuable read, it covers passing std::string_view and SSO, too.
    – lubgr
    Aug 7 at 7:34
















  • With pass by reference, Creature c("John"); makes an extra copy
    – immibis
    Aug 7 at 5:46






  • 1




    This link might be a valuable read, it covers passing std::string_view and SSO, too.
    – lubgr
    Aug 7 at 7:34















With pass by reference, Creature c("John"); makes an extra copy
– immibis
Aug 7 at 5:46




With pass by reference, Creature c("John"); makes an extra copy
– immibis
Aug 7 at 5:46




1




1




This link might be a valuable read, it covers passing std::string_view and SSO, too.
– lubgr
Aug 7 at 7:34




This link might be a valuable read, it covers passing std::string_view and SSO, too.
– lubgr
Aug 7 at 7:34












4 Answers
4






active

oldest

votes

















up vote
12
down vote



accepted











  1. Did I understand correctly what is happening here?



Yes.




  1. Is there any upside of using std::move over passing by reference and just calling m_namename?



An easy to grasp function signature without any additional overloads. The signature immediately reveals that the argument will be copied - this saves callers from wondering whether a const std::string& reference might be stored as a data member, possibly becoming a dangling reference later on. And there is no need to overload on std::string&& name and const std::string& arguments to avoid unnecessary copies when rvalues are passed to the function. Passing an lvalue



std::string nameString("Alex");
Creature c(nameString);


to the function that takes its argument by value causes one copy and one move construction. Passing an rvalue to the same function



std::string nameString("Alex");
Creature c(std::move(nameString));


causes two move constructions. In contrast, when the function parameter is const std::string&, there will always be a copy, even when passing an rvalue argument. This is clearly an advantage as long as the argument type is cheap to move-construct (this is the case for std::string).



But there is a downside to consider: the reasoning doesn't work for functions that assign the function argument to another variable (instead of initializing it):



void setName(std::string name)

m_name = std::move(name);



will cause a deallocation of the resource that m_name refers to before it's reassigned. I recommend reading Item 41 in Effective Modern C++ and also this question.






share|improve this answer























  • That makes sense, especially that this makes the declaration more intuitive to read. I'm not sure I fully grasp the deallocation part of your answer (and understand the linked thread), so just to check If I use move, the space gets deallocated. If I don't use move, it only gets deallocated if the allocated space is too small to hold the new string, leading to improved performance. Is that correct?
    – Blackbot
    Aug 6 at 12:36







  • 1




    Yes, that's exactly it. When assigning to m_name from a const std::string& parameter, the internal memory is re-used as long as m_name fits in. When move-assigning m_name, the memory must be deallocated beforehand. Otherwise, it was impossible to "steal" the resources from the right hand side of the assignment.
    – lubgr
    Aug 6 at 12:38


















up vote
39
down vote













/* (0) */ 
Creature(const std::string &name) : m_namename


  • A passed lvalue binds to name, then is copied into m_name.


  • A passed rvalue binds to name, then is copied into m_name.



/* (1) */ 
Creature(std::string name) : m_namestd::move(name)


  • A passed lvalue is copied into name, then is moved into m_name.


  • A passed rvalue is moved into name, then is moved into m_name.



/* (2) */ 
Creature(const std::string &name) : m_namename
Creature(std::string &&rname) : m_namestd::move(rname)


  • A passed lvalue binds to name, then is copied into m_name.


  • A passed rvalue binds to rname, then is moved into m_name.



As move operations are usually faster than copies, (1) is better than (0) if you pass a lot of temporaries. (2) is optimal in terms of copies/moves, but requires code repetition.



The code repetition can be avoided with perfect forwarding:



/* (3) */
template <typename T,
std::enable_if_t<
std::is_convertible_v<std::remove_cvref_t<T>, std::string>,
int> = 0
>
Creature(T&& name) : m_namestd::forward<T>(name)


You might optionally want to constrain T in order to restrict the domain of types that this constructor can be instantiated with (as shown above). C++20 aims to simplify this with Concepts.




In C++17, prvalues are affected by guaranteed copy elision, which - when applicable - will reduce the number of copies/moves when passing arguments to functions.






share|improve this answer























  • For (1) the pr-value and xvalue case are not identical since c++17 no?
    – Oliv
    Aug 6 at 11:55







  • 1




    Note that you don't need the SFINAE to perfect forward in this case. It's only needed to disambiguate. It's plausibly helpful for the potential error messages when passing bad arguments
    – Caleth
    Aug 6 at 15:57










  • @Oliv Yes. xvalues need to be moved, while prvalues can be ellided away :)
    – Rakete1111
    Aug 7 at 4:59











  • in (1), why do you need the std::move(rname)? std::move is a cast to a rvalue, rname is a xvalue; shouldn't the xvalue be moved anyway even without the explicit cast?
    – Ant
    Aug 7 at 9:19










  • @Ant: the rvalue reference itself is accessed through a name, which means it's an lvalue. You need to cast it to an rvalue to propagate its temporariness
    – Vittorio Romeo
    Aug 7 at 14:54

















up vote
1
down vote













How you pass is not the only variable here, what you pass makes the big difference between the two.



In C++, we have all kinds of value categories and this "idiom" exists for cases where you pass in an rvalue (such as "Alex-string-literal-that-constructs-temporary-std::string" or std::move(nameString)), which results in 0 copies of std::string being made (the type does not even have to be copy-constructible for rvalue arguments), and only uses std::string's move constructor.



Somewhat related Q&A.






share|improve this answer






























    up vote
    1
    down vote













    There are several disadvantages of pass-by-value-and-move approach over pass-by-(rv)reference:



    • it causes 3 objects to be spawned instead of 2;

    • passing an object by value may lead to extra stack overhead, because even regular string class is typically at least 3 or 4 times larger than a pointer;

    • argument objects construction is going to be done on the caller side, causing code bloat;





    share|improve this answer





















    • Could you clarify why it would cause 3 objects to spawn? From what I understand I can just pass "Peter" as a string. This would get spawned, copied and then moved, wouldn't it? And wouldn't the stack be used at some point regardless? Not at the point of the constructor call, but in the m_namename part where it gets copied?
      – Blackbot
      Aug 6 at 12:43










    • @Blackbot I was referring to your example std::string nameString("Alex"); Creature c(nameString); one object is nameString, another is function argument, and third one is a class field.
      – VTT
      Aug 6 at 17:57










    Your Answer





    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "1"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: false,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );








     

    draft saved


    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f51705967%2fadvantages-of-pass-by-value-and-stdmove-over-pass-by-reference%23new-answer', 'question_page');

    );

    Post as a guest






























    4 Answers
    4






    active

    oldest

    votes








    4 Answers
    4






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    12
    down vote



    accepted











    1. Did I understand correctly what is happening here?



    Yes.




    1. Is there any upside of using std::move over passing by reference and just calling m_namename?



    An easy to grasp function signature without any additional overloads. The signature immediately reveals that the argument will be copied - this saves callers from wondering whether a const std::string& reference might be stored as a data member, possibly becoming a dangling reference later on. And there is no need to overload on std::string&& name and const std::string& arguments to avoid unnecessary copies when rvalues are passed to the function. Passing an lvalue



    std::string nameString("Alex");
    Creature c(nameString);


    to the function that takes its argument by value causes one copy and one move construction. Passing an rvalue to the same function



    std::string nameString("Alex");
    Creature c(std::move(nameString));


    causes two move constructions. In contrast, when the function parameter is const std::string&, there will always be a copy, even when passing an rvalue argument. This is clearly an advantage as long as the argument type is cheap to move-construct (this is the case for std::string).



    But there is a downside to consider: the reasoning doesn't work for functions that assign the function argument to another variable (instead of initializing it):



    void setName(std::string name)

    m_name = std::move(name);



    will cause a deallocation of the resource that m_name refers to before it's reassigned. I recommend reading Item 41 in Effective Modern C++ and also this question.






    share|improve this answer























    • That makes sense, especially that this makes the declaration more intuitive to read. I'm not sure I fully grasp the deallocation part of your answer (and understand the linked thread), so just to check If I use move, the space gets deallocated. If I don't use move, it only gets deallocated if the allocated space is too small to hold the new string, leading to improved performance. Is that correct?
      – Blackbot
      Aug 6 at 12:36







    • 1




      Yes, that's exactly it. When assigning to m_name from a const std::string& parameter, the internal memory is re-used as long as m_name fits in. When move-assigning m_name, the memory must be deallocated beforehand. Otherwise, it was impossible to "steal" the resources from the right hand side of the assignment.
      – lubgr
      Aug 6 at 12:38















    up vote
    12
    down vote



    accepted











    1. Did I understand correctly what is happening here?



    Yes.




    1. Is there any upside of using std::move over passing by reference and just calling m_namename?



    An easy to grasp function signature without any additional overloads. The signature immediately reveals that the argument will be copied - this saves callers from wondering whether a const std::string& reference might be stored as a data member, possibly becoming a dangling reference later on. And there is no need to overload on std::string&& name and const std::string& arguments to avoid unnecessary copies when rvalues are passed to the function. Passing an lvalue



    std::string nameString("Alex");
    Creature c(nameString);


    to the function that takes its argument by value causes one copy and one move construction. Passing an rvalue to the same function



    std::string nameString("Alex");
    Creature c(std::move(nameString));


    causes two move constructions. In contrast, when the function parameter is const std::string&, there will always be a copy, even when passing an rvalue argument. This is clearly an advantage as long as the argument type is cheap to move-construct (this is the case for std::string).



    But there is a downside to consider: the reasoning doesn't work for functions that assign the function argument to another variable (instead of initializing it):



    void setName(std::string name)

    m_name = std::move(name);



    will cause a deallocation of the resource that m_name refers to before it's reassigned. I recommend reading Item 41 in Effective Modern C++ and also this question.






    share|improve this answer























    • That makes sense, especially that this makes the declaration more intuitive to read. I'm not sure I fully grasp the deallocation part of your answer (and understand the linked thread), so just to check If I use move, the space gets deallocated. If I don't use move, it only gets deallocated if the allocated space is too small to hold the new string, leading to improved performance. Is that correct?
      – Blackbot
      Aug 6 at 12:36







    • 1




      Yes, that's exactly it. When assigning to m_name from a const std::string& parameter, the internal memory is re-used as long as m_name fits in. When move-assigning m_name, the memory must be deallocated beforehand. Otherwise, it was impossible to "steal" the resources from the right hand side of the assignment.
      – lubgr
      Aug 6 at 12:38













    up vote
    12
    down vote



    accepted







    up vote
    12
    down vote



    accepted







    1. Did I understand correctly what is happening here?



    Yes.




    1. Is there any upside of using std::move over passing by reference and just calling m_namename?



    An easy to grasp function signature without any additional overloads. The signature immediately reveals that the argument will be copied - this saves callers from wondering whether a const std::string& reference might be stored as a data member, possibly becoming a dangling reference later on. And there is no need to overload on std::string&& name and const std::string& arguments to avoid unnecessary copies when rvalues are passed to the function. Passing an lvalue



    std::string nameString("Alex");
    Creature c(nameString);


    to the function that takes its argument by value causes one copy and one move construction. Passing an rvalue to the same function



    std::string nameString("Alex");
    Creature c(std::move(nameString));


    causes two move constructions. In contrast, when the function parameter is const std::string&, there will always be a copy, even when passing an rvalue argument. This is clearly an advantage as long as the argument type is cheap to move-construct (this is the case for std::string).



    But there is a downside to consider: the reasoning doesn't work for functions that assign the function argument to another variable (instead of initializing it):



    void setName(std::string name)

    m_name = std::move(name);



    will cause a deallocation of the resource that m_name refers to before it's reassigned. I recommend reading Item 41 in Effective Modern C++ and also this question.






    share|improve this answer
















    1. Did I understand correctly what is happening here?



    Yes.




    1. Is there any upside of using std::move over passing by reference and just calling m_namename?



    An easy to grasp function signature without any additional overloads. The signature immediately reveals that the argument will be copied - this saves callers from wondering whether a const std::string& reference might be stored as a data member, possibly becoming a dangling reference later on. And there is no need to overload on std::string&& name and const std::string& arguments to avoid unnecessary copies when rvalues are passed to the function. Passing an lvalue



    std::string nameString("Alex");
    Creature c(nameString);


    to the function that takes its argument by value causes one copy and one move construction. Passing an rvalue to the same function



    std::string nameString("Alex");
    Creature c(std::move(nameString));


    causes two move constructions. In contrast, when the function parameter is const std::string&, there will always be a copy, even when passing an rvalue argument. This is clearly an advantage as long as the argument type is cheap to move-construct (this is the case for std::string).



    But there is a downside to consider: the reasoning doesn't work for functions that assign the function argument to another variable (instead of initializing it):



    void setName(std::string name)

    m_name = std::move(name);



    will cause a deallocation of the resource that m_name refers to before it's reassigned. I recommend reading Item 41 in Effective Modern C++ and also this question.







    share|improve this answer















    share|improve this answer



    share|improve this answer








    edited Aug 6 at 11:17


























    answered Aug 6 at 11:12









    lubgr

    4,606732




    4,606732











    • That makes sense, especially that this makes the declaration more intuitive to read. I'm not sure I fully grasp the deallocation part of your answer (and understand the linked thread), so just to check If I use move, the space gets deallocated. If I don't use move, it only gets deallocated if the allocated space is too small to hold the new string, leading to improved performance. Is that correct?
      – Blackbot
      Aug 6 at 12:36







    • 1




      Yes, that's exactly it. When assigning to m_name from a const std::string& parameter, the internal memory is re-used as long as m_name fits in. When move-assigning m_name, the memory must be deallocated beforehand. Otherwise, it was impossible to "steal" the resources from the right hand side of the assignment.
      – lubgr
      Aug 6 at 12:38

















    • That makes sense, especially that this makes the declaration more intuitive to read. I'm not sure I fully grasp the deallocation part of your answer (and understand the linked thread), so just to check If I use move, the space gets deallocated. If I don't use move, it only gets deallocated if the allocated space is too small to hold the new string, leading to improved performance. Is that correct?
      – Blackbot
      Aug 6 at 12:36







    • 1




      Yes, that's exactly it. When assigning to m_name from a const std::string& parameter, the internal memory is re-used as long as m_name fits in. When move-assigning m_name, the memory must be deallocated beforehand. Otherwise, it was impossible to "steal" the resources from the right hand side of the assignment.
      – lubgr
      Aug 6 at 12:38
















    That makes sense, especially that this makes the declaration more intuitive to read. I'm not sure I fully grasp the deallocation part of your answer (and understand the linked thread), so just to check If I use move, the space gets deallocated. If I don't use move, it only gets deallocated if the allocated space is too small to hold the new string, leading to improved performance. Is that correct?
    – Blackbot
    Aug 6 at 12:36





    That makes sense, especially that this makes the declaration more intuitive to read. I'm not sure I fully grasp the deallocation part of your answer (and understand the linked thread), so just to check If I use move, the space gets deallocated. If I don't use move, it only gets deallocated if the allocated space is too small to hold the new string, leading to improved performance. Is that correct?
    – Blackbot
    Aug 6 at 12:36





    1




    1




    Yes, that's exactly it. When assigning to m_name from a const std::string& parameter, the internal memory is re-used as long as m_name fits in. When move-assigning m_name, the memory must be deallocated beforehand. Otherwise, it was impossible to "steal" the resources from the right hand side of the assignment.
    – lubgr
    Aug 6 at 12:38





    Yes, that's exactly it. When assigning to m_name from a const std::string& parameter, the internal memory is re-used as long as m_name fits in. When move-assigning m_name, the memory must be deallocated beforehand. Otherwise, it was impossible to "steal" the resources from the right hand side of the assignment.
    – lubgr
    Aug 6 at 12:38













    up vote
    39
    down vote













    /* (0) */ 
    Creature(const std::string &name) : m_namename


    • A passed lvalue binds to name, then is copied into m_name.


    • A passed rvalue binds to name, then is copied into m_name.



    /* (1) */ 
    Creature(std::string name) : m_namestd::move(name)


    • A passed lvalue is copied into name, then is moved into m_name.


    • A passed rvalue is moved into name, then is moved into m_name.



    /* (2) */ 
    Creature(const std::string &name) : m_namename
    Creature(std::string &&rname) : m_namestd::move(rname)


    • A passed lvalue binds to name, then is copied into m_name.


    • A passed rvalue binds to rname, then is moved into m_name.



    As move operations are usually faster than copies, (1) is better than (0) if you pass a lot of temporaries. (2) is optimal in terms of copies/moves, but requires code repetition.



    The code repetition can be avoided with perfect forwarding:



    /* (3) */
    template <typename T,
    std::enable_if_t<
    std::is_convertible_v<std::remove_cvref_t<T>, std::string>,
    int> = 0
    >
    Creature(T&& name) : m_namestd::forward<T>(name)


    You might optionally want to constrain T in order to restrict the domain of types that this constructor can be instantiated with (as shown above). C++20 aims to simplify this with Concepts.




    In C++17, prvalues are affected by guaranteed copy elision, which - when applicable - will reduce the number of copies/moves when passing arguments to functions.






    share|improve this answer























    • For (1) the pr-value and xvalue case are not identical since c++17 no?
      – Oliv
      Aug 6 at 11:55







    • 1




      Note that you don't need the SFINAE to perfect forward in this case. It's only needed to disambiguate. It's plausibly helpful for the potential error messages when passing bad arguments
      – Caleth
      Aug 6 at 15:57










    • @Oliv Yes. xvalues need to be moved, while prvalues can be ellided away :)
      – Rakete1111
      Aug 7 at 4:59











    • in (1), why do you need the std::move(rname)? std::move is a cast to a rvalue, rname is a xvalue; shouldn't the xvalue be moved anyway even without the explicit cast?
      – Ant
      Aug 7 at 9:19










    • @Ant: the rvalue reference itself is accessed through a name, which means it's an lvalue. You need to cast it to an rvalue to propagate its temporariness
      – Vittorio Romeo
      Aug 7 at 14:54














    up vote
    39
    down vote













    /* (0) */ 
    Creature(const std::string &name) : m_namename


    • A passed lvalue binds to name, then is copied into m_name.


    • A passed rvalue binds to name, then is copied into m_name.



    /* (1) */ 
    Creature(std::string name) : m_namestd::move(name)


    • A passed lvalue is copied into name, then is moved into m_name.


    • A passed rvalue is moved into name, then is moved into m_name.



    /* (2) */ 
    Creature(const std::string &name) : m_namename
    Creature(std::string &&rname) : m_namestd::move(rname)


    • A passed lvalue binds to name, then is copied into m_name.


    • A passed rvalue binds to rname, then is moved into m_name.



    As move operations are usually faster than copies, (1) is better than (0) if you pass a lot of temporaries. (2) is optimal in terms of copies/moves, but requires code repetition.



    The code repetition can be avoided with perfect forwarding:



    /* (3) */
    template <typename T,
    std::enable_if_t<
    std::is_convertible_v<std::remove_cvref_t<T>, std::string>,
    int> = 0
    >
    Creature(T&& name) : m_namestd::forward<T>(name)


    You might optionally want to constrain T in order to restrict the domain of types that this constructor can be instantiated with (as shown above). C++20 aims to simplify this with Concepts.




    In C++17, prvalues are affected by guaranteed copy elision, which - when applicable - will reduce the number of copies/moves when passing arguments to functions.






    share|improve this answer























    • For (1) the pr-value and xvalue case are not identical since c++17 no?
      – Oliv
      Aug 6 at 11:55







    • 1




      Note that you don't need the SFINAE to perfect forward in this case. It's only needed to disambiguate. It's plausibly helpful for the potential error messages when passing bad arguments
      – Caleth
      Aug 6 at 15:57










    • @Oliv Yes. xvalues need to be moved, while prvalues can be ellided away :)
      – Rakete1111
      Aug 7 at 4:59











    • in (1), why do you need the std::move(rname)? std::move is a cast to a rvalue, rname is a xvalue; shouldn't the xvalue be moved anyway even without the explicit cast?
      – Ant
      Aug 7 at 9:19










    • @Ant: the rvalue reference itself is accessed through a name, which means it's an lvalue. You need to cast it to an rvalue to propagate its temporariness
      – Vittorio Romeo
      Aug 7 at 14:54












    up vote
    39
    down vote










    up vote
    39
    down vote









    /* (0) */ 
    Creature(const std::string &name) : m_namename


    • A passed lvalue binds to name, then is copied into m_name.


    • A passed rvalue binds to name, then is copied into m_name.



    /* (1) */ 
    Creature(std::string name) : m_namestd::move(name)


    • A passed lvalue is copied into name, then is moved into m_name.


    • A passed rvalue is moved into name, then is moved into m_name.



    /* (2) */ 
    Creature(const std::string &name) : m_namename
    Creature(std::string &&rname) : m_namestd::move(rname)


    • A passed lvalue binds to name, then is copied into m_name.


    • A passed rvalue binds to rname, then is moved into m_name.



    As move operations are usually faster than copies, (1) is better than (0) if you pass a lot of temporaries. (2) is optimal in terms of copies/moves, but requires code repetition.



    The code repetition can be avoided with perfect forwarding:



    /* (3) */
    template <typename T,
    std::enable_if_t<
    std::is_convertible_v<std::remove_cvref_t<T>, std::string>,
    int> = 0
    >
    Creature(T&& name) : m_namestd::forward<T>(name)


    You might optionally want to constrain T in order to restrict the domain of types that this constructor can be instantiated with (as shown above). C++20 aims to simplify this with Concepts.




    In C++17, prvalues are affected by guaranteed copy elision, which - when applicable - will reduce the number of copies/moves when passing arguments to functions.






    share|improve this answer















    /* (0) */ 
    Creature(const std::string &name) : m_namename


    • A passed lvalue binds to name, then is copied into m_name.


    • A passed rvalue binds to name, then is copied into m_name.



    /* (1) */ 
    Creature(std::string name) : m_namestd::move(name)


    • A passed lvalue is copied into name, then is moved into m_name.


    • A passed rvalue is moved into name, then is moved into m_name.



    /* (2) */ 
    Creature(const std::string &name) : m_namename
    Creature(std::string &&rname) : m_namestd::move(rname)


    • A passed lvalue binds to name, then is copied into m_name.


    • A passed rvalue binds to rname, then is moved into m_name.



    As move operations are usually faster than copies, (1) is better than (0) if you pass a lot of temporaries. (2) is optimal in terms of copies/moves, but requires code repetition.



    The code repetition can be avoided with perfect forwarding:



    /* (3) */
    template <typename T,
    std::enable_if_t<
    std::is_convertible_v<std::remove_cvref_t<T>, std::string>,
    int> = 0
    >
    Creature(T&& name) : m_namestd::forward<T>(name)


    You might optionally want to constrain T in order to restrict the domain of types that this constructor can be instantiated with (as shown above). C++20 aims to simplify this with Concepts.




    In C++17, prvalues are affected by guaranteed copy elision, which - when applicable - will reduce the number of copies/moves when passing arguments to functions.







    share|improve this answer















    share|improve this answer



    share|improve this answer








    edited Aug 6 at 19:07


























    answered Aug 6 at 11:25









    Vittorio Romeo

    49.9k14134272




    49.9k14134272











    • For (1) the pr-value and xvalue case are not identical since c++17 no?
      – Oliv
      Aug 6 at 11:55







    • 1




      Note that you don't need the SFINAE to perfect forward in this case. It's only needed to disambiguate. It's plausibly helpful for the potential error messages when passing bad arguments
      – Caleth
      Aug 6 at 15:57










    • @Oliv Yes. xvalues need to be moved, while prvalues can be ellided away :)
      – Rakete1111
      Aug 7 at 4:59











    • in (1), why do you need the std::move(rname)? std::move is a cast to a rvalue, rname is a xvalue; shouldn't the xvalue be moved anyway even without the explicit cast?
      – Ant
      Aug 7 at 9:19










    • @Ant: the rvalue reference itself is accessed through a name, which means it's an lvalue. You need to cast it to an rvalue to propagate its temporariness
      – Vittorio Romeo
      Aug 7 at 14:54
















    • For (1) the pr-value and xvalue case are not identical since c++17 no?
      – Oliv
      Aug 6 at 11:55







    • 1




      Note that you don't need the SFINAE to perfect forward in this case. It's only needed to disambiguate. It's plausibly helpful for the potential error messages when passing bad arguments
      – Caleth
      Aug 6 at 15:57










    • @Oliv Yes. xvalues need to be moved, while prvalues can be ellided away :)
      – Rakete1111
      Aug 7 at 4:59











    • in (1), why do you need the std::move(rname)? std::move is a cast to a rvalue, rname is a xvalue; shouldn't the xvalue be moved anyway even without the explicit cast?
      – Ant
      Aug 7 at 9:19










    • @Ant: the rvalue reference itself is accessed through a name, which means it's an lvalue. You need to cast it to an rvalue to propagate its temporariness
      – Vittorio Romeo
      Aug 7 at 14:54















    For (1) the pr-value and xvalue case are not identical since c++17 no?
    – Oliv
    Aug 6 at 11:55





    For (1) the pr-value and xvalue case are not identical since c++17 no?
    – Oliv
    Aug 6 at 11:55





    1




    1




    Note that you don't need the SFINAE to perfect forward in this case. It's only needed to disambiguate. It's plausibly helpful for the potential error messages when passing bad arguments
    – Caleth
    Aug 6 at 15:57




    Note that you don't need the SFINAE to perfect forward in this case. It's only needed to disambiguate. It's plausibly helpful for the potential error messages when passing bad arguments
    – Caleth
    Aug 6 at 15:57












    @Oliv Yes. xvalues need to be moved, while prvalues can be ellided away :)
    – Rakete1111
    Aug 7 at 4:59





    @Oliv Yes. xvalues need to be moved, while prvalues can be ellided away :)
    – Rakete1111
    Aug 7 at 4:59













    in (1), why do you need the std::move(rname)? std::move is a cast to a rvalue, rname is a xvalue; shouldn't the xvalue be moved anyway even without the explicit cast?
    – Ant
    Aug 7 at 9:19




    in (1), why do you need the std::move(rname)? std::move is a cast to a rvalue, rname is a xvalue; shouldn't the xvalue be moved anyway even without the explicit cast?
    – Ant
    Aug 7 at 9:19












    @Ant: the rvalue reference itself is accessed through a name, which means it's an lvalue. You need to cast it to an rvalue to propagate its temporariness
    – Vittorio Romeo
    Aug 7 at 14:54




    @Ant: the rvalue reference itself is accessed through a name, which means it's an lvalue. You need to cast it to an rvalue to propagate its temporariness
    – Vittorio Romeo
    Aug 7 at 14:54










    up vote
    1
    down vote













    How you pass is not the only variable here, what you pass makes the big difference between the two.



    In C++, we have all kinds of value categories and this "idiom" exists for cases where you pass in an rvalue (such as "Alex-string-literal-that-constructs-temporary-std::string" or std::move(nameString)), which results in 0 copies of std::string being made (the type does not even have to be copy-constructible for rvalue arguments), and only uses std::string's move constructor.



    Somewhat related Q&A.






    share|improve this answer



























      up vote
      1
      down vote













      How you pass is not the only variable here, what you pass makes the big difference between the two.



      In C++, we have all kinds of value categories and this "idiom" exists for cases where you pass in an rvalue (such as "Alex-string-literal-that-constructs-temporary-std::string" or std::move(nameString)), which results in 0 copies of std::string being made (the type does not even have to be copy-constructible for rvalue arguments), and only uses std::string's move constructor.



      Somewhat related Q&A.






      share|improve this answer

























        up vote
        1
        down vote










        up vote
        1
        down vote









        How you pass is not the only variable here, what you pass makes the big difference between the two.



        In C++, we have all kinds of value categories and this "idiom" exists for cases where you pass in an rvalue (such as "Alex-string-literal-that-constructs-temporary-std::string" or std::move(nameString)), which results in 0 copies of std::string being made (the type does not even have to be copy-constructible for rvalue arguments), and only uses std::string's move constructor.



        Somewhat related Q&A.






        share|improve this answer















        How you pass is not the only variable here, what you pass makes the big difference between the two.



        In C++, we have all kinds of value categories and this "idiom" exists for cases where you pass in an rvalue (such as "Alex-string-literal-that-constructs-temporary-std::string" or std::move(nameString)), which results in 0 copies of std::string being made (the type does not even have to be copy-constructible for rvalue arguments), and only uses std::string's move constructor.



        Somewhat related Q&A.







        share|improve this answer















        share|improve this answer



        share|improve this answer








        edited Aug 6 at 11:22


























        answered Aug 6 at 11:12









        LogicStuff

        15.4k63656




        15.4k63656




















            up vote
            1
            down vote













            There are several disadvantages of pass-by-value-and-move approach over pass-by-(rv)reference:



            • it causes 3 objects to be spawned instead of 2;

            • passing an object by value may lead to extra stack overhead, because even regular string class is typically at least 3 or 4 times larger than a pointer;

            • argument objects construction is going to be done on the caller side, causing code bloat;





            share|improve this answer





















            • Could you clarify why it would cause 3 objects to spawn? From what I understand I can just pass "Peter" as a string. This would get spawned, copied and then moved, wouldn't it? And wouldn't the stack be used at some point regardless? Not at the point of the constructor call, but in the m_namename part where it gets copied?
              – Blackbot
              Aug 6 at 12:43










            • @Blackbot I was referring to your example std::string nameString("Alex"); Creature c(nameString); one object is nameString, another is function argument, and third one is a class field.
              – VTT
              Aug 6 at 17:57














            up vote
            1
            down vote













            There are several disadvantages of pass-by-value-and-move approach over pass-by-(rv)reference:



            • it causes 3 objects to be spawned instead of 2;

            • passing an object by value may lead to extra stack overhead, because even regular string class is typically at least 3 or 4 times larger than a pointer;

            • argument objects construction is going to be done on the caller side, causing code bloat;





            share|improve this answer





















            • Could you clarify why it would cause 3 objects to spawn? From what I understand I can just pass "Peter" as a string. This would get spawned, copied and then moved, wouldn't it? And wouldn't the stack be used at some point regardless? Not at the point of the constructor call, but in the m_namename part where it gets copied?
              – Blackbot
              Aug 6 at 12:43










            • @Blackbot I was referring to your example std::string nameString("Alex"); Creature c(nameString); one object is nameString, another is function argument, and third one is a class field.
              – VTT
              Aug 6 at 17:57












            up vote
            1
            down vote










            up vote
            1
            down vote









            There are several disadvantages of pass-by-value-and-move approach over pass-by-(rv)reference:



            • it causes 3 objects to be spawned instead of 2;

            • passing an object by value may lead to extra stack overhead, because even regular string class is typically at least 3 or 4 times larger than a pointer;

            • argument objects construction is going to be done on the caller side, causing code bloat;





            share|improve this answer













            There are several disadvantages of pass-by-value-and-move approach over pass-by-(rv)reference:



            • it causes 3 objects to be spawned instead of 2;

            • passing an object by value may lead to extra stack overhead, because even regular string class is typically at least 3 or 4 times larger than a pointer;

            • argument objects construction is going to be done on the caller side, causing code bloat;






            share|improve this answer













            share|improve this answer



            share|improve this answer











            answered Aug 6 at 11:36









            VTT

            20.8k32143




            20.8k32143











            • Could you clarify why it would cause 3 objects to spawn? From what I understand I can just pass "Peter" as a string. This would get spawned, copied and then moved, wouldn't it? And wouldn't the stack be used at some point regardless? Not at the point of the constructor call, but in the m_namename part where it gets copied?
              – Blackbot
              Aug 6 at 12:43










            • @Blackbot I was referring to your example std::string nameString("Alex"); Creature c(nameString); one object is nameString, another is function argument, and third one is a class field.
              – VTT
              Aug 6 at 17:57
















            • Could you clarify why it would cause 3 objects to spawn? From what I understand I can just pass "Peter" as a string. This would get spawned, copied and then moved, wouldn't it? And wouldn't the stack be used at some point regardless? Not at the point of the constructor call, but in the m_namename part where it gets copied?
              – Blackbot
              Aug 6 at 12:43










            • @Blackbot I was referring to your example std::string nameString("Alex"); Creature c(nameString); one object is nameString, another is function argument, and third one is a class field.
              – VTT
              Aug 6 at 17:57















            Could you clarify why it would cause 3 objects to spawn? From what I understand I can just pass "Peter" as a string. This would get spawned, copied and then moved, wouldn't it? And wouldn't the stack be used at some point regardless? Not at the point of the constructor call, but in the m_namename part where it gets copied?
            – Blackbot
            Aug 6 at 12:43




            Could you clarify why it would cause 3 objects to spawn? From what I understand I can just pass "Peter" as a string. This would get spawned, copied and then moved, wouldn't it? And wouldn't the stack be used at some point regardless? Not at the point of the constructor call, but in the m_namename part where it gets copied?
            – Blackbot
            Aug 6 at 12:43












            @Blackbot I was referring to your example std::string nameString("Alex"); Creature c(nameString); one object is nameString, another is function argument, and third one is a class field.
            – VTT
            Aug 6 at 17:57




            @Blackbot I was referring to your example std::string nameString("Alex"); Creature c(nameString); one object is nameString, another is function argument, and third one is a class field.
            – VTT
            Aug 6 at 17:57












             

            draft saved


            draft discarded


























             


            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f51705967%2fadvantages-of-pass-by-value-and-stdmove-over-pass-by-reference%23new-answer', 'question_page');

            );

            Post as a guest













































































            Comments

            Popular posts from this blog

            Long meetings (6-7 hours a day): Being “babysat” by supervisor

            Is the Concept of Multiple Fantasy Races Scientifically Flawed? [closed]

            Confectionery