Efficient push_back of classes and structs - c++

I've seen my colleague do the second snippet quite often. Why is this? I've tried adding print statements to track the ctors and dtors, but both seem identical.
std::vector<ClassTest> vecClass1;
ClassTest ct1;
ct1.blah = blah // set some stuff
...
vecClass1.push_back(ct1);
std::vector<ClassTest> vecClass2;
vecClass2.push_back(ClassTest());
ClassTest& ct2 = vecClass2.back();
ct2.blah = blah // set some stuff
...
PS. I'm sorry if the title is misleading.
Edit:
Firstly, thank you all for your responses.
I've written a small application using std::move. The results are surprising to me perhaps because I've done something wrong ... would someone please explain why the "fast" path is performing significantly better.
#include <vector>
#include <string>
#include <boost/progress.hpp>
#include <iostream>
const std::size_t SIZE = 10*100*100*100;
//const std::size_t SIZE = 1;
const bool log = (SIZE == 1);
struct SomeType {
std::string who;
std::string bio;
SomeType() {
if (log) std::cout << "SomeType()" << std::endl;
}
SomeType(const SomeType& other) {
if (log) std::cout << "SomeType(const SomeType&)" << std::endl;
//this->who.swap(other.who);
//this->bio.swap(other.bio);
this->who = other.who;
this->bio = other.bio;
}
SomeType& operator=(SomeType& other) {
if (log) std::cout << "SomeType::operator=()" << std::endl;
this->who.swap(other.who);
this->bio.swap(other.bio);
return *this;
}
~SomeType() {
if (log) std::cout << "~SomeType()" << std::endl;
}
void swap(SomeType& other) {
if (log) std::cout << "Swapping" << std::endl;
this->who.swap(other.who);
this->bio.swap(other.bio);
}
// move semantics
SomeType(SomeType&& other) :
who(std::move(other.who))
, bio(std::move(other.bio)) {
if (log) std::cout << "SomeType(SomeType&&)" << std::endl;
}
SomeType& operator=(SomeType&& other) {
if (log) std::cout << "SomeType::operator=(SomeType&&)" << std::endl;
this->who = std::move(other.who);
this->bio = std::move(other.bio);
return *this;
}
};
int main(int argc, char** argv) {
{
boost::progress_timer time_taken;
std::vector<SomeType> store;
std::cout << "Timing \"slow\" path" << std::endl;
for (std::size_t i = 0; i < SIZE; ++i) {
SomeType some;
some.who = "bruce banner the hulk";
some.bio = "you do not want to see me angry";
//store.push_back(SomeType());
//store.back().swap(some);
store.push_back(std::move(some));
}
}
{
boost::progress_timer time_taken;
std::vector<SomeType> store;
std::cout << "Timing \"fast\" path" << std::endl;
for (std::size_t i = 0; i < SIZE; ++i) {
store.push_back(SomeType());
SomeType& some = store.back();
some.who = "bruce banner the hulk";
some.bio = "you do not want to see me angry";
}
}
return 0;
}
Output:
dev#ubuntu-10:~/Desktop/perf_test$ g++ -Wall -O3 push_back-test.cpp -std=c++0x
dev#ubuntu-10:~/Desktop/perf_test$ ./a.out
Timing "slow" path
3.36 s
Timing "fast" path
3.08 s

If the object is more expensive to copy after "set some stuff" than before, then the copy that happens when you insert the object into the vector will be less expensive if you insert the object before you "set some stuff" than after.
Really, though, since you should expect objects in a vector to be copied occasionally, this is probably not much of an optimization.

If we accept that your colleague's snippet is wise, because ClassTest is expensive to copy, I would prefer:
using std::swap;
std::vector<ClassTest> vecClass1;
ClassTest ct1;
ct1.blah = blah // set some stuff
...
vecClass1.push_back(ClassTest());
swap(ct1, vecClass1.back());
I think it's clearer, and it may well be more exception-safe. The ... code presumably allocates resources and hence could throw an exception (or else what's making the fully-built ClassTest so expensive to copy?). So unless the vector really is local to the function, I don't think it's a good idea for it to be half-built while running that code.
Of course this is even more expensive if ClassTest only has the default swap implementation, but if ClassTest doesn't have an efficient swap, then it has no business being expensive to copy. So this trick perhaps should only be used with classes known to be friendly, rather than unknown template parameter types.
As Gene says, std::move is better anyway, if you have that C++0x feature.
If we're worried about ClassTest being expensive to copy, though, then relocating the vector is a terrifying prospect. So we should also either:
reserve enough space before adding anything,
use a deque instead of a vector.

The second version benefits from moving the temporary. The first version is copying the temporary vector. So the second one is potentially faster. The second version has also potentially smaller peak memory requirements, the first version creates two objects one temporary and one copy of it and only then deletes the temporary. You can improve the first version by explicitly moving the temporary:
std::vector<ClassTest> vecClass1;
ClassTest ct1;
ct1.blah = blah // set some stuff
...
vecClass1.push_back(std::move(ct1));

You should probably ask your collegue to know exactly why, but we can still take a guess. As James pointed out, it might be a tad more efficient if the object is more expensive to copy once constructed.
I see advantages in both versions.
I like your collegue's snippet because: although there are 2 objects in both cases, they only co-exist for a very short period of time in the second version. There is only one object available for editing: this avoids the potential error of editing ct1 after push_back.
I like your personal snippet because: invoking push_back to add a second object potentially invalidates the reference ct2, inducing a risk of undefined behavior. The first snippet does not present this risk.

They are identical (as far as I can see). Maybe he or she does that as an idiomatic custom.

Related

Why this code does allow to push_back unique_ptr do vector?

so I thought adding unique to vector shouldn't work.
Why does it work for the below code?
Is it cause by not setting copy ctor as "deleted"??
#include <iostream>
#include <vector>
#include <memory>
class Test
{
public:
int i = 5;
};
int main()
{
std::vector<std::unique_ptr<Test>> tests;
tests.push_back(std::make_unique<Test>());
for (auto &test : tests)
{
std::cout << test->i << std::endl;
}
for (auto &test : tests)
{
std::cout << test->i << std::endl;
}
}
There is no copy here, only moves.
In this context, make_unique will produce an instance of unique pointer which is not named, and this push_back sees it as a r-value reference, which it can use as it wants.
It produce pretty much the same result than this code would:
std::vector<std::unique_ptr<Test>> tests;
auto ptr = std::make_unique<Test>();
tests.push_back(std::move(ptr));
This is called move semantics if you want to search more info on the matter. (and this only works from c++11 and beyond)
There are two overloads of std::vector::push_back according to https://en.cppreference.com/w/cpp/container/vector/push_back
In your case you will use the one with rvalue-ref so no copying required.

How to choose between `push_*()` and `emplace_*()` functions?

I understand the difference between the two function variants.
My question is: should I normally use good old push_*() version and only switch to emplace_*() when my profiler tells me this will benefit performance (that is, do not optimise prematurely)? Or should I switch to using emplace_*() as the default (perhaps not to pessimise the code unnecessarily - similar to i++ vs ++i in for loops)?
Is any of the variants more universal than the other (that is, imposes less constraints on the type being inserted) in realistic non-contrived use cases?
While writing the code I would not worry about performance. Performance is for later when you already have code that you can profile.
I'd rather worry about expressiveness of the code. Roughly speaking, push_back is for when you have an element and want to place a copy inside the container. emplace_back is to construct the element in place.
Consider what has the lower "wtf-count":
struct foo {int x;int y;};
void foo_add(const foo& f,std::vector<foo>& v) {
v.emplace_back(f); // wtf ?!? we already have a foo
v.push_back(f); // ... simply make a copy (or move)
}
void foo_add(int x, int y, std::vector<foo>& v) {
auto z = foo{x,y}; // wtf ?!?
f.push_back(z); // why create a temporary?
f.emplace_back(x,y); // ... simply construct it in place
}
emplace functions are delegating constructors.
Let's say you have a container of T.
If you already have a T, maybe it's const, maybe it's a rvalue, maybe none of those;
Then you use push_xxx().
Your object will be copied/moved into the container.
If you instead want to construct a T, then you use emplace_xxx(), with the same parameters you would send the constructor.
An object will be constructed directly in the container.
Emplace functions are more generic than push functions. In no case they are less efficient, on the contrary - they can be more efficient, as they allow to optimize away one copy/move operation of the container element when you need to construct it from arguments. When putting an element into container involves copy/move anyway, emplace and push operations are equivalent.
Push can be preferable, if you actually want to enforce construction before copying/moving the element into the container. For example, if your element type has some special logic in its constructor that you want to execute before the container is modified. Such cases are quite rare, though.
If you switch from push_back to emplace_back in a naive way, you will have no advantage at all. Consider the following code:
#include <iostream>
#include <string>
#include <vector>
struct President
{
std::string name;
std::string country;
int year;
President(std::string p_name, std::string p_country, int p_year) :
name(std::move(p_name)), country(std::move(p_country)), year(p_year)
{
std::cout << "I am being constructed.\n";
}
President(President&& other) :
name(std::move(other.name)), country(std::move(other.country)),
year(other.year)
{
std::cout << "I am being moved.\n";
}
President& operator=(const President& other) = default;
};
int main()
{
std::vector<President> elections;
std::cout << "emplace_back:\n";
elections.emplace_back("Nelson Mandela", "South Africa", 1994);
std::vector<President> reElections;
std::cout << "\npush_back:\n";
reElections.push_back(
President("Franklin Delano Roosevelt", "the USA", 1936));
std::cout << "\nContents:\n";
for (President const& president : elections)
{
std::cout << president.name << " was elected president of "
<< president.country << " in " << president.year << ".\n";
}
for (President const& president : reElections)
{
std::cout << president.name << " was re-elected president of "
<< president.country << " in " << president.year << ".\n";
}
}
If you replace push_back by emplace_back you still have a construction and then a move. Only if you pass the arguments needed for construction instead of the constructed instance itself (see the call to emplace_back), you have saved effort.

How to find C++ spurious copy operations?

Recently, I had the following
struct data {
std::vector<int> V;
};
data get_vector(int n)
{
std::vector<int> V(n,0);
return {V};
}
The problem with this code is that when the struct is created a copy occurs and the solution is instead to write return {std::move(V)}
Are there linter or code analyzer that would detect such spurious copy operations? Neither cppcheck, cpplint, nor clang-tidy can do it.
EDIT: Several points to make my question clearer:
I know that a copy operation occurred because I used compiler explorer and it shows a call to memcpy.
I could identify that a copy operations occurred by looking at the standard yes. But my initial wrong idea was that the compiler would optimize away this copy. I was wrong.
It is (likely) not a compiler problem since both clang and gcc produce code that produce a memcpy.
The memcpy may be cheap, but I cannot imagine circumstances where copying memory and deleting the original is cheaper than passing a pointer by a std::move.
The adding of the std::move is an elementary operation. I would imagine that a code analyzer would be able to suggest this correction.
I believe you have the correct observation but the wrong interpretation!
The copy will not occur by returning the value, because every normal clever compiler will use (N)RVO in this case. From C++17 this is mandatory, so you can't see any copy by returning a local generated vector from the function.
OK, lets play a bit with std::vector and what will happen during the construction or by filling it step by step.
First of all, lets generate a data type which makes every copy or move visible like this one:
template <typename DATA >
struct VisibleCopy
{
private:
DATA data;
public:
VisibleCopy( const DATA& data_ ): data{ data_ }
{
std::cout << "Construct " << data << std::endl;
}
VisibleCopy( const VisibleCopy& other ): data{ other.data }
{
std::cout << "Copy " << data << std::endl;
}
VisibleCopy( VisibleCopy&& other ) noexcept : data{ std::move(other.data) }
{
std::cout << "Move " << data << std::endl;
}
VisibleCopy& operator=( const VisibleCopy& other )
{
data = other.data;
std::cout << "copy assign " << data << std::endl;
}
VisibleCopy& operator=( VisibleCopy&& other ) noexcept
{
data = std::move( other.data );
std::cout << "move assign " << data << std::endl;
}
DATA Get() const { return data; }
};
And now lets start some experiments:
using T = std::vector< VisibleCopy<int> >;
T Get1()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec{ 1,2,3,4 };
std::cout << "End init" << std::endl;
return vec;
}
T Get2()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec(4,0);
std::cout << "End init" << std::endl;
return vec;
}
T Get3()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
T Get4()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.reserve(4);
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
int main()
{
auto vec1 = Get1();
auto vec2 = Get2();
auto vec3 = Get3();
auto vec4 = Get4();
// All data as expected? Lets check:
for ( auto& el: vec1 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec2 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec3 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec4 ) { std::cout << el.Get() << std::endl; }
}
What can we observe:
Example 1)
We create a vector from a initializer list and maybe we expect that we will see 4 times construct and 4 moves. But we get 4 copies! That sounds a bit mysterious, but the reason is the implementation of initializer list! Simply it is not allowed to move from the list as the iterator from the list is a const T* which makes it impossible to move elements from it. A detailed answer on this topic can be found here: initializer_list and move semantics
Example 2)
In this case, we get a initial construction and 4 copies of the value. That is nothing special and is what we can expect.
Example 3)
Also here, we the the construction and some moves as expected. With my stl implementation the vector grows by factor 2 every time. So we see a first construct, another one and because the vector resizes from 1 to 2, we see the move of the first element. While adding the 3 one, we see a resize from 2 to 4 which needs a move of the first two elements. All as expected!
Example 4)
Now we reserve space and fill later. Now we have no copy and no move anymore!
In all cases, we do not see any move nor copy by returning the vector back to the caller at all! (N)RVO is taking place and no further action is required in this step!
Back to your question:
"How to find C++ spurious copy operations"
As seen above, you may introduce a proxy class in between for debugging purpose.
Making the copy-ctor private may not work in many cases, as you may have some wanted copies and some hidden ones. As above, only the code for example 4 will work with a private copy-ctor! And I can not answer the question, if the example 4 is the fastest one, as we fill peace by peace.
Sorry that I can not offer a general solution for finding "unwanted" copies here. Even if you dig your code for calls of memcpy, you will not find all as also memcpy will be optimized away and you see directly some assembler instructions doing the job without a call to your library memcpy function.
My hint is not to focus on such a minor problem. If you have real performance issues, take a profiler and measure. There are so many potential performance killers, that investing much time on spurious memcpy usage seems not such a worthwhile idea.
I know that a copy operation occurred because I used compiler explorer and it shows a call to memcpy.
Did you put your complete application into the compiler explorer, and did you enable optimizations? If not, then what you saw in the compiler explorer might or might not be what is happening with your application.
One issue with the code you posted is that you first create a std::vector, and then copy it into an instance of data. It would be better to initialize data with the vector:
data get_vector(int n)
{
return {std::vector<int> V(n,0)};
}
Also, if you just give the compiler explorer the definition of data and get_vector(), and nothing else, it has to expect the worse. If you actually give it some source code that uses get_vector(), then look at what assembly is generated for that source code. See this example for what the above modification plus actual usage plus compiler optimizations can cause the compiler to produce.

What are the pros and cons of receiving a variable as a reference with std::shared_ptr?

I just wondering if the following way of delivering a pointer variable, created inside of the func1, to the caller (func2) is a correct way of doing this. If this is correct, will it release the memory when func2 is returned? If it is a bad idea, why is that?
int & func1()
{
std::shared_ptr<int> d = std::make_shared<int>(50);
return *d;
}
void func2(){
int & dd = func1();
}
This is a simplified code. I am assuming the size of d is huge(e.g images).
Added:
I realized that the following also works. What will be the pros and cons of each approach?
std::shared_ptr<int> & func1()
{
std::shared_ptr<int> d = std::make_shared<int>(50);
return d;
}
void func2()
{
std::shared_ptr<int> & dd = func1();
}
Both of those examples are bad. You can't use the return values of either func1, they are always dangling references.
int & func1()
{
std::shared_ptr<int> d = std::make_shared<int>(50);
return *d;
} // d is the only owner when it is destroyed, *d is also deleted
std::shared_ptr<int> & func1()
{
std::shared_ptr<int> d = std::make_shared<int>(50);
return d;
} // d is destroyed here
I am assuming the size of d is huge
You are mistaken. The size of the object pointed-to by d has no bearing on the size of d, just like with raw pointers.
E.g.
#include <iostream>
#include <memory>
struct Huge
{
int data[100000];
};
int main()
{
std::cout << sizeof(int) << std::endl
<< sizeof(int*) << std::endl
<< sizeof(std::shared_ptr<int>) << std::endl
<< sizeof(std::unique_ptr<int>) << std::endl
<< sizeof(Huge) << std::endl
<< sizeof(Huge*) << std::endl
<< sizeof(std::shared_ptr<Huge>) << std::endl
<< sizeof(std::unique_ptr<Huge>) << std::endl;
}
for me results in
4
8
16
8
400000
8
16
8
I realized that the following also works
If by works, you mean "is accepted by a C++ compiler", then yes. They both result in undefined behaviour if you use the references returned, so I would say they categorically don't work.
I wanted to make a simple comment out of this, but there was too much to be said. This is meant to be a precision of mathematician1975's comment, which in my opinion is a good point to bring up.
I thought just returning shared_ptr will require extra cost to copy the control block.
You may want to have a look at (N)RVO/copy elision, which is precisely the mechanism avoiding this kind of thing: https://en.cppreference.com/w/cpp/language/copy_elision.
Long story short: returning it won't copy it, but instead will build it in-place at caller's site. No performance cost!
I've made a basic live example which shows how (N)RVO works, available here: http://coliru.stacked-crooked.com/a/8a32afc3775c685e
EDIT: If it can help clarifying the process of all this, I've written an article about copy elision and [N]RVO.

Is it possible to avoid managing memory manually in this situation in c++?

I have a Storage class that keeps a list of Things:
#include <iostream>
#include <list>
#include <functional>
class Thing {
private:
int id;
int value = 0;
static int nextId;
public:
Thing() { this->id = Thing::nextId++; };
int getId() const { return this->id; };
int getValue() const { return this->value; };
void add(int n) { this->value += n; };
};
int Thing::nextId = 1;
class Storage {
private:
std::list<std::reference_wrapper<Thing>> list;
public:
void add(Thing& thing) {
this->list.push_back(thing);
}
Thing& findById(int id) const {
for (std::list<std::reference_wrapper<Thing>>::const_iterator it = this->list.begin(); it != this->list.end(); ++it) {
if (it->get().getId() == id) return *it;
}
std::cout << "Not found!!\n";
exit(1);
}
};
I started with a simple std::list<Thing>, but then everything is copied around on insertion and retrieval, and I didn't want this because if I get a copy, altering it does not reflect on the original objects anymore. When looking for a solution to that, I found about std::reference_wrapper on this SO question, but now I have another problem.
Now to the code that uses them:
void temp(Storage& storage) {
storage.findById(2).add(1);
Thing t4; t4.add(50);
storage.add(t4);
std::cout << storage.findById(4).getValue() << "\n";
}
void run() {
Thing t1; t1.add(10);
Thing t2; t2.add(100);
Thing t3; t3.add(1000);
Storage storage;
storage.add(t3);
storage.add(t1);
storage.add(t2);
temp(storage);
t2.add(10000);
std::cout << storage.findById(2).getValue() << "\n";
std::cout << storage.findById(4).getValue() << "\n";
}
My main() simply calls run(). The output I get is:
50
10101
Not found!!
Although I was looking for:
50
10101
50
Question
Looks like the locally declared object t4 ceases to exist when the function returns, which makes sense. I could prevent this by dynamically allocating it, using new, but then I didn't want to manage memory manually...
How can I fix the code without removing the temp() function and without having to manage memory manually?
If I just use a std::list<Thing> as some suggested, surely the problem with t4 and temp will cease to exist, but another problem will arise: the code won't print 10101 anymore, for example. If I keep copying stuff around, I won't be able to alter the state of a stored object.
Who is the owner of the Thing in the Storage?
Your actual problem is ownership. Currently, your Storage does not really contain the Things but instead it is left to the user of the Storage to manage the lifetime of the objects you put inside it. This is very much against the philosophy of std containers. All standard C++ containers own the objects you put in them and the container manages their lifetime (eg you simply call v.resize(v.size()-2) on a vector and the last two elements get destroyed).
Why references?
You already found a way to make the container not own the actual objects (by using a reference_wrapper), but there is no reason to do so. Of a class called Storage I would expect it to hold objects not just references. Moreover, this opens the door to lots of nasty problems, including undefined behaviour. For example here:
void temp(Storage& storage) {
storage.findById(2).add(1);
Thing t4; t4.add(50);
storage.add(t4);
std::cout << storage.findById(4).getValue() << "\n";
}
you store a reference to t4 in the storage. The thing is: t4s lifetime is only till the end of that function and you end up with a dangling reference. You can store such a reference, but it isnt that usefull because you are basically not allowed to do anything with it.
Aren't references a cool thing?
Currently you can push t1, modify it, and then observe that changes on the thingy in Storage, this might be fine if you want to mimic Java, but in c++ we are used to containers making a copy when you push something (there are also methods to create the elements in place, in case you worry about some useless temporaries). And yes, of course, if you really want you can make a standard container also hold references, but lets make a small detour...
Who collects all that garbage?
Maybe it helps to consider that Java is garbage-collected while C++ has destructors. In Java you are used to references floating around till the garbage collector kicks in. In C++ you have to be super aware of the lifetime of your objects. This may sound bad, but acutally it turns out to be extremely usefull to have full control over the lifetime of objects.
Garbage? What garbage?
In modern C++ you shouldnt worry to forget a delete, but rather appreciate the advantages of having RAII. Acquiring resources on initialzation and knowing when a destructor gets called allows to get automatic resource management for basically any kind of resource, something a garbage collector can only dream of (think of files, database connections, etc.).
"How can I fix the code without removing the temp() function and without having to manage memory manually?"
A trick that helped me a lot is this: Whenever I find myself thinking I need to manage a resource manually I stop and ask "Can't someone else do the dirty stuff?". It is really extremely rare that I cannot find a standard container that does exactly what I need out of the box. In your case, just let the std::list do the "dirty" work.
Can't be C++ if there is no template, right?
I would actually suggest you to make Storage a template, along the line of:
template <typename T>
class Storage {
private:
std::list<T> list;
//....
Then
Storage<Thing> thing_storage;
Storage<int> int_storage;
are Storages containing Things and ints, respectively. In that way, if you ever feel like exprimenting with references or pointers you could still instantiate a Storage<reference_wrapper<int>>.
Did I miss something?...maybe references?
I won't be able to alter the state of a stored object
Given that the container owns the object you would rather let the user take a reference to the object in the container. For example with a vector that would be
auto t = std::vector<int>(10,0); // 10 element initialized to 0
auto& first_element = t[0]; // reference to first element
first_element = 5; // first_element is an alias for t[0]
std::cout << t[0]; // i dont want to spoil the fun part
To make this work with your Storage you just have to make findById return a reference. As a demo:
struct foo {
private:
int data;
public:
int& get_ref() { return data;}
const int& get_ref() const { return data;}
};
auto x = foo();
x.get_ref = 12;
TL;DR
How to avoid manual resource managment? Let someone else do it for you and call it automatic resource management :P
t4 is a temporary object that is destroyed at exit from temp() and what you store in storage becomes a dangling reference, causing UB.
It is not quite clear what you're trying to achieve, but if you want to keep the Storage class the same as it is, you should make sure that all the references stored into it are at least as long-lived as the storage itself. This you have discovered is one of the reasons STL containers keep their private copies of elements (others, probably less important, being—elimination of an extra indirection and a much better locality in some cases).
P.S. And please, can you stop writing those this-> and learn about initialization lists in constructors? >_<
In terms of what your code actually appears to be doing, you've definitely overcomplicated your code, by my estimation. Consider this code, which does all the same things your code does, but with far less boilerplate code and in a way that's far more safe for your uses:
#include<map>
#include<iostream>
int main() {
std::map<int, int> things;
int & t1 = things[1];
int & t2 = things[2];
int & t3 = things[3];
t1 = 10;
t2 = 100;
t3 = 1000;
t2++;
things[4] = 50;
std::cout << things.at(4) << std::endl;
t2 += 10000;
std::cout << things.at(2) << std::endl;
std::cout << things.at(4) << std::endl;
things.at(2) -= 75;
std::cout << things.at(2) << std::endl;
std::cout << t2 << std::endl;
}
//Output:
50
10101
50
10026
10026
Note that a few interesting things are happening here:
Because t2 is a reference, and insertion into the map doesn't invalidate references, t2 can be modified, and those modifications will be reflected in the map itself, and vise-versa.
things owns all the values that were inserted into it, and it will be cleaned up due to RAII, and the built-in behavior of std::map, and the broader C++ design principles it is obeying. There's no worry about objects not being cleaned up.
If you need to preserve the behavior where the id incrementing is handled automatically, independently from the end-programmer, we could consider this code instead:
#include<map>
#include<iostream>
int & insert(std::map<int, int> & things, int value) {
static int id = 1;
int & ret = things[id++] = value;
return ret;
}
int main() {
std::map<int, int> things;
int & t1 = insert(things, 10);
int & t2 = insert(things, 100);
int & t3 = insert(things, 1000);
t2++;
insert(things, 50);
std::cout << things.at(4) << std::endl;
t2 += 10000;
std::cout << things.at(2) << std::endl;
std::cout << things.at(4) << std::endl;
things.at(2) -= 75;
std::cout << things.at(2) << std::endl;
std::cout << t2 << std::endl;
}
//Output:
50
10101
50
10026
10026
These code snippets should give you a decent sense of how the language works, and what principles, possibly unfamiliar in the code I've written, that you need to learn about. My general recommendation is to find a good C++ resource for learning the basics of the language, and learn from that. Some good resources can be found here.
One last thing: if the use of Thing is critical to your code, because you need more data saved in the map, consider this instead:
#include<map>
#include<iostream>
#include<string>
//Only difference between struct and class is struct sets everything public by default
struct Thing {
int value;
double rate;
std::string name;
Thing() : Thing(0,0,"") {}
Thing(int value, double rate, std::string name) : value(value), rate(rate), name(std::move(name)) {}
};
int main() {
std::map<int, Thing> things;
Thing & t1 = things[1];
t1.value = 10;
t1.rate = 5.7;
t1.name = "First Object";
Thing & t2 = things[2];
t2.value = 15;
t2.rate = 17.99999;
t2.name = "Second Object";
t2.value++;
std::cout << things.at(2).value << std::endl;
t1.rate *= things.at(2).rate;
std::cout << things.at(1).rate << std::endl;
std::cout << t1.name << "," << things.at(2).name << std::endl;
things.at(1).rate -= 17;
std::cout << t1.rate << std::endl;
}
Based on what François Andrieux and Eljay have said (and what I would have said, had I got there first), here is the way I would do it, if you want to mutate objects you have already added to a list. All that reference_wrapper stuff is just a fancy way of passing pointers around. It will end in tears.
OK. here's the code (now edited as per OP's request):
#include <iostream>
#include <list>
#include <memory>
class Thing {
private:
int id;
int value = 0;
static int nextId;
public:
Thing() { this->id = Thing::nextId++; };
int getId() const { return this->id; };
int getValue() const { return this->value; };
void add(int n) { this->value += n; };
};
int Thing::nextId = 1;
class Storage {
private:
std::list<std::shared_ptr<Thing>> list;
public:
void add(const std::shared_ptr<Thing>& thing) {
this->list.push_back(thing);
}
std::shared_ptr<Thing> findById(int id) const {
for (std::list<std::shared_ptr<Thing>>::const_iterator it = this->list.begin(); it != this->list.end(); ++it) {
if (it->get()->getId() == id) return *it;
}
std::cout << "Not found!!\n";
exit(1);
}
};
void add_another(Storage& storage) {
storage.findById(2)->add(1);
std::shared_ptr<Thing> t4 = std::make_shared<Thing> (); t4->add(50);
storage.add(t4);
std::cout << storage.findById(4)->getValue() << "\n";
}
int main() {
std::shared_ptr<Thing> t1 = std::make_shared<Thing> (); t1->add(10);
std::shared_ptr<Thing> t2 = std::make_shared<Thing> (); t2->add(100);
std::shared_ptr<Thing> t3 = std::make_shared<Thing> (); t3->add(1000);
Storage storage;
storage.add(t3);
storage.add(t1);
storage.add(t2);
add_another(storage);
t2->add(10000);
std::cout << storage.findById(2)->getValue() << "\n";
std::cout << storage.findById(4)->getValue() << "\n";
return 0;
}
Output is now:
50
10101
50
as desired. Run it on Wandbox.
Note that what you are doing here, in effect, is reference counting your Things. The Things themselves are never copied and will go away when the last shared_ptr goes out of scope. Only the shared_ptrs are copied, and they are designed to be copied because that's their job. Doing things this way is almost as efficient as passing references (or wrapped references) around and far safer. When starting out, it's easy to forget that a reference is just a pointer in disguise.
Given that your Storage class does not own the Thing objects, and every Thing object is uniquely counted, why not just store Thing* in the list?
class Storage {
private:
std::list<Thing*> list;
public:
void add(Thing& thing) {
this->list.push_back(&thing);
}
Thing* findById(int id) const {
for (auto thing : this->list) {
if (thing->getId() == id) return thing;
}
std::cout << "Not found!!\n";
return nullptr;
}
};
EDIT: Note that Storage::findById now returns Thing* which allows it to fail gracefully by returning nullptr (rather than exit(1)).