3

I have just read about the rvalue references and below is the code I am referring to

vector<string> v;
string s = "hello";   

v.push_back(s); 
cout << s << endl;
cout << v[0] << endl;
return 0

My question is that till now, whenever I have seen vector of objects (or strings), I mostly see inserts being done like above. Instead if I did v.push_back(std::move(s)) then I am not unnecessarily creating duplicate objects.

Should this be the correct way of adding objects to STL where all we care about is the data in container and not otherwise variables.

Omnifarious
  • 54,333
  • 19
  • 131
  • 194
Kraken
  • 23,393
  • 37
  • 102
  • 162

2 Answers2

5

Given the comments I've gotten, I've decided on a different approach to this answer. Here is a program that uses various ways of adding an object to a vector. This program prints out exactly which copy constructors, move constructors, copy operators, move operators, and destructors are called in all cases.

The program uses the marvelous fmt library. And fmt::print does exactly what you think it does. I should, perhaps, use ::fmt. Keep in mind that moving from an object leaves it in an unspecified (note, not the same as undefined) state, in general. Of course, for classes you write yourself, you know exactly what state it's in. But for standard library classes, or classes written by other people, you don't. Apparently, for the standard library classes you are guaranteed that if you do something to set the state to something known, that the object will indeed change to that state.

Personally, I would just treat the object as if the only valid operation on it after you move from it is to call its destructor. But I can see cases in which you might simply want to re-use the storage in some way.

Here is the program:

#include <fmt/core.h> // fmt
#include <vector>  // vector
#include <utility> // move

class TestClass {
 public:
    TestClass(int a, int b) {
        fmt::print("TestClass{{{}, {}}}\n", a, b);
    }
    TestClass(TestClass const &) noexcept {
        fmt::print("TestClass{{TestClass const &}}\n");
    }
    TestClass(TestClass &&) noexcept {
        fmt::print("TestClass{{TestClass &&}}\n");
    }
    TestClass const &operator =(TestClass const &) noexcept {
        fmt::print("=(TestClass const &)\n");
        return *this;
    }
    TestClass const &operator =(TestClass &&) noexcept {
        fmt::print("=(TestClass &&)\n");
        return *this;
    }
    ~TestClass() noexcept {
        fmt::print("~TestClass()\n");
    }
};

int main()
{
   ::std::vector<TestClass> v;
   // Reserve necessary space so movements of vector elements doesn't clutter up
   // the output.
   v.reserve(6);
   fmt::print("Constructing initial\n");
   TestClass o{1, 2};

   fmt::print("\bv.push_back(o)\n");
   v.push_back(o);

   fmt::print("\nv.push_back(::std::move(o))\n");
   v.push_back(::std::move(o));

   fmt::print("\nv.push_back(TestClass{{3, 4}})\n");
   v.push_back(TestClass{3, 4});

   fmt::print("\nv.emplace_back(5, 6)\n");
   v.emplace_back(5, 6);

   fmt::print("\nv.emplace_back(::std::move(o))\n");
   v.emplace_back(::std::move(o));

   fmt::print("\nv.emplace_back(TestClass{{5, 6}})\n");
   v.emplace_back(TestClass{5, 6});

   fmt::print("\nHere H\n");
}

Here is the program's output:

Constructing initial
TestClass{1, 2}
v.push_back(o)
TestClass{TestClass const &}

v.push_back(::std::move(o))
TestClass{TestClass &&}

v.push_back(TestClass{3, 4})
TestClass{3, 4}
TestClass{TestClass &&}
~TestClass()

v.emplace_back(5, 6)
TestClass{5, 6}

v.emplace_back(::std::move(o))
TestClass{TestClass &&}

v.emplace_back(TestClass{5, 6})
TestClass{5, 6}
TestClass{TestClass &&}
~TestClass()

Here H
~TestClass()
~TestClass()
~TestClass()
~TestClass()
~TestClass()
~TestClass()
~TestClass()

I will say that this program has exactly the output I expected, and that output is (AFAIK) consistent with the answer I had here before.


Why I use ::std:: instead of std::

Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • 1
    One cannot use std::move in this scenario, so it's not less efficient, it's simply wrong. In scenarios where one can use both move semantics and emplace_back, one *should* normally use *both*. – n. m. could be an AI Jan 27 '19 at 17:39
  • @n.m.- Please explain, do you have any references? – Omnifarious Jan 27 '19 at 17:41
  • In this particular case both emplace_back and push_back involve one copy constructor, so they are nearly indistinguishable as far as performance goes. emplace_back is more efficient when a constructor other than the copy constructor is called. – n. m. could be an AI Jan 27 '19 at 17:46
  • @n.m. In the OPs code? I presumed his or her decision to print the string after adding it was just to see what the moving it did. Though, of course, the only valid thing to do to an object that has been moved from is to destroy it. – Omnifarious Jan 27 '19 at 18:48
  • There is a rather long list of statements here, which ones you are interested in most? – n. m. could be an AI Jan 27 '19 at 21:31
  • @n.m. - I don't know. I don't really understand what you're saying at all. I thought I had a really good handle on `emplace` and `push_back` and how they interacted with move semantics. Your reply makes me think that I know absolutely nothing about the topic. So I don't know what I should ask to reach a state where I feel like I know what I'm talking about anymore. I'm actually feeling like maybe I ought to just delete my answer. – Omnifarious Jan 28 '19 at 00:40
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/187409/discussion-between-n-m-and-omnifarious). – n. m. could be an AI Jan 28 '19 at 06:11
  • 1
    @Omnifarious _"Though, of course, the only valid thing to do to an object that has been moved from is to destroy it."_ Not true in general. You can do anything that has no preconditions, and/or which returns to a known state. – Jonathan Wakely Jan 28 '19 at 07:17
0

Q: What should be used?

//emplace_back constructs the elements in place.

emplace_back("element");

//push_back will create new object and then copy(or move) its value of arguments.

push_back(explicitDataType{"element"});

So here you should use emplace_back() instead of std::move().

Hamza.S
  • 1,319
  • 9
  • 18
  • [vector::push_back](https://en.cppreference.com/w/cpp/container/vector/push_back) understands rvalues, so doesn't make unnecessary copies, _as long as the parameter is the same type as the vector_. – Mooing Duck Jan 29 '19 at 00:56