0

I have code like this:

#include <iostream>
#include <exception>
#include <stdexcept>
#include <string>
#include <vector>
#include <utility>
     
struct Foo {
    Foo(std::string t):text(t){}
    //destructor deleted
        
    std::string text;
};

int main()
{
    std::vector<Foo> v;
        
    v.push_back(Foo("foo 1"));
    v.push_back(Foo("foo 2"));
    v.push_back(Foo("foo 3"));
      
    for(auto& foo : v){
        std::cout<<foo.text<<"\n";
    }
      
    Foo fooBack = std::move(v.back());
      
    std::cout<<fooBack.text<<"\n";
      
    for(auto& foo : v){
        std::cout<<foo.text<<"\n";
    }
}

std::move() in this case doesn't steal the return variable. Is there a way to avoid the excess copy?

This is the result:

foo 1
foo 2
foo 3
foo 3
foo 1
foo 2
foo 3

new code based on sugestions(dont know if keep asking on same thread dont know if that was what i expected , the reaon i was doind all this move is to reduce code):

#include <iostream>
#include <exception>
#include <stdexcept>

#include<string>
#include<vector>
#include<utility>
 
struct Foo {
        Foo(std::string t):text(t){
            std::cout<<text<<" construct foo called \n";
        }
        Foo(const Foo& f){
            text = f.text;
            std::cout<<text<<"copy construct foo called \n";
        }
        Foo(Foo&& f){
            text = std::move(f.text);
            std::cout<<text<<"move construct foo called \n";
        
        }
        /*
    ~Foo() {
    }
    */
    
    std::string text;
};
int main()
{
    
    std::vector<Foo> v;
    
  v.emplace_back("foo 1");
  v.emplace_back("foo 2");
  v.emplace_back("foo 3");
  
  for(auto&& foo : v){
    std::cout<<foo.text<<"\n";
  }
  
  Foo fooBack = std::move(v.back());
  v.pop_back();
  
  std::cout<<fooBack.text<<"\n";
  
  for(auto&& foo : v){
    std::cout<<foo.text<<"\n";
  }
  
}

new results to see it in action

foo 1 construct foo called 
foo 2 construct foo called 
foo 1copy construct foo called 
foo 3 construct foo called 
foo 1copy construct foo called 
foo 2copy construct foo called 
foo 1
foo 2
foo 3
foo 3move construct foo called 
foo 3
foo 1
foo 2

8
  • 1
    Get rid of the empty destructor. Writing empty destructors can cause the compiler to generate less-efficient code, as seen here Commented Jun 23, 2020 at 19:19
  • You dont need to move a temporary. If you want to avoid a copy, try providing your class a move constructor. Commented Jun 23, 2020 at 19:20
  • 1
    It occurred to me to mention this, I think you might misunderstand std::move it doesn't move anything. All it does is perform a cast. Commented Jun 23, 2020 at 19:23
  • 1
    @user3709120 std::move() itself is just a type-cast to an rvalue reference, it doesn't steal anything on its own. It is actually the job of move constructors and move assignment operators that take rvalue references as input to do the actual stealing. Commented Jun 23, 2020 at 19:53
  • 1
    It's entirely possible that the moved-from Foo object's text remains unchanged after being moved from due to short string optimization. The state of a std::string is undefined after being moved from; it is not guaranteed to be empty. Commented Jun 23, 2020 at 19:55

1 Answer 1

4

The statement Foo fooBack = std::move(v.back()); does not remove the last Foo object from the vector at all, but it does move the content of that object to your fooBack variable, due to a compiler-generated move constructor for Foo. However, the original Foo object itself is still in the vector, which is why your 2nd for loop still sees that object. The object just may not have a text value anymore to print, because fooBack stole it.

Live Demo

That being said, it is possible that in your case, std::string is utilizing "Short String Optimization" (where small string values are kept in a fixed buffer inside the std::string class itself to avoid allocating memory dynamically when not needed) in such a way that a "move" from one std::string to another simply makes a copy of that fixed buffer's data, leaving the original std::string unchanged. Which would explain why your 2nd for loop is still showing "foo 3" in the vector after "moving" v.back() to the fooBack variable - it was really a copy, not a move.

If you want to actually remove the last Foo object from the vector, you can use v.pop_back() for that, eg:

Foo fooBack = std::move(v.back());
v.pop_back(); // <-- add this

Live Demo

On a side note, your push_back()'s should be using emplace_back() instead, to avoid creating temporary Foo objects that have to be moved from during the pushes:

//v.push_back(Foo("foo 1"));
//v.push_back(Foo("foo 2"));
//v.push_back(Foo("foo 3"));
v.emplace_back("foo 1");
v.emplace_back("foo 2");
v.emplace_back("foo 3");
Sign up to request clarification or add additional context in comments.

6 Comments

I think part of OP's confusion comes from the fact that the moved-from object's text isn't empty after being moved from in their example output (likely due to short string optimization). It may be worthwhile to mention that a std::string is not guaranteed to be empty after being moved from.
@MilesBudnek "the moved-from object's text isn't empty after being moved from" - it should be. It does in my test. "It may be worthwhile to mention that a std::string is not guaranteed to be empty after being moved from" - yes, it is. Whether SSO is used or not, operator<< will still do the right thing.
@MilesBudnek That may be what the standard says, but in practice it doesn't make sense for an implementation of a std::string move constructor/assignment to leave data behind in the source std::string. It wouldn't be much of a move if it did. But whatever, I have added it to my answer.
The whole point of the standard is to define behavior that you can rely on. While its true that it doesn't make any sense to leave data behind in the string, its not up to the standard to impose the restriction that it must zero out that data. The platform you're compiling on might just cause that to be a very costly operation. Hence the wording. Its best not to rely on it being empty. You shouldn't use an object after you move from it anyways, except maybe to assign it new data or "reset" it in some way. i.e. you shouldn't care if it is or isn't empty.
@Taekahn "You shouldn't use an object after you move from it anyways, except maybe to assign it new data or "reset" it in some way" - true enough.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.