Replacing shared_ptr elements in std::vector - c++

I have a vector of shared_ptrs as below.
std::vector<std::shared_ptr<SharedThing>> things;
Now let's say I push a number of shared_ptrs onto the vector and each element now has a reference count of 1.
When I need to replace one of those elements with a new shared_ptr I want the old shared_ptr to go out of scope. Will regular element assignment achieve this or will it just copy the shared_ptr contents. For example:
things.at(0) = new_shared_ptr;
Will this decrement the reference count of things.at(0) and increment the count of new_shared_ptr?

When I need to replace one of those elements with a new shared_ptr I
want the old shared_ptr to go out of scope. Will regular element
assignment achieve this?
The shared pointer in the vector won't go out of scope,
but it will replace the managed object with the new one given.
Calling:
things.at(0) = new_shared_ptr;
will preserve the count at 1.
Here is an easy way to observe this behaviour:
#include <iostream>
#include <vector>
#include <memory>
int main(){
//vector with a shared pointer
std::vector<std::shared_ptr<int>> things;
things.push_back(std::make_shared<int>(1));
//prints 1
std::cout << things.at(0).use_count() << '\n';
//assign a new value
things.at(0) = std::make_shared<int>(2);
//still prints 1
std::cout << things.at(0).use_count() << '\n';
}
Although not a part of your question, it is often advised to use make_shared instead of a new.

Yes, basically you are right.
To be more accurate, the reference count of previous shared_ptr at(0) will be decremented. And then you assign it with a new shared_ptr, which may have the count 1. Looks like the reference count at(0) is the same, but it changed and changed back.
You can verify it by std::shared_ptr::use_cout()
For more details, we can debug into the STL, when
things.at(0) = new_shared_ptr;
include/c++/4.8.3/bits/shared_ptr_base.h:556
__shared_count&
operator=(const __shared_count& __r) noexcept
{
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if (__tmp != _M_pi)
{
if (__tmp != 0)
__tmp->_M_add_ref_copy();
if (_M_pi != 0)
_M_pi->_M_release();
_M_pi = __tmp;
}
return *this;
}
The new one _M_add_ref_copy(), then previous one _M_release(), which will decrease the _M_use_count by 1.

Related

Passing std::vector::data to function expecting type** (double pointer)

As the title describes, I am trying to pass the pointer to the data of a std::vector into a function expecting a double pointer. Take as an example the code below. I have an int pointer d which is passed to myfunc1 as &d (still not sure if call it the pointer's reference or what), where the function changes its reference to the beginning of an int array filled with 1,2,3,4. However, if I have a std::vector of ints and try to pass &(vec.data()) to myfunc1 the compiler throws the error lvalue required as unary ‘&’ operand. I have already tried something like (int *)&(vec.data()) as per this answer, but it does not work.
Just for reference, I know I can do something like myfunc2 where I directly pass the vector as reference and the job is done. But I want to know if it's possible to use myfunc1 with the std::vector's pointer.
Any help will be very much appreciated.
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
void myfunc1(int** ptr)
{
int* values = new int[4];
// Fill all the with data
for(auto& i:{0,1,2,3})
{
values[i] = i+1;
}
*ptr = values;
}
void myfunc2(vector<int> &vec)
{
int* values = new int[4];
// Fill all the with data
for(auto& i:{0,1,2,3})
{
values[i] = i+1;
}
vec.assign(values,values+4);
delete values;
}
int main()
{
// Create int pointer
int* d;
// This works. Reference of d pointing to the array
myfunc1(&d);
// Print values
for(auto& i:{0,1,2,3})
{
cout << d[i] << " ";
}
cout << endl;
// Creates the vector
vector<int> vec;
// This works. Data pointer of std::vector pointing to the array
myfunc2(vec);
// Print values
for (const auto &element : vec) cout << element << " ";
cout << endl;
// This does not work
vector<int> vec2;
vec2.resize(4);
myfunc1(&(vec2.data()));
// Print values
for (const auto &element : vec2) cout << element << " ";
cout << endl;
return 0;
}
EDIT: What my actual code does is to read some binary files from disk, and load parts of the buffer into the vector. I was having troubles getting the modified vector out of a read function, and this is what I came up with that allowed me to solve it.
When you write:
myfunc1(&(vec2.data()));
You are getting the address of a rvalue. The pointed int* is so a temporary that is destroyed right after the call.
This is why you get this error.
But, as #molbdnilo said, in your myfunc1() function, you are reassigning the pointer (without caring to destroy previously allocated memory by the way).
But the std::vector already manages its data memory on its own. You cannot and you must not put your hands on it.
What my actual code does is to read some binary files from disk, and load parts of the buffer into the vector.
A solution could be to construct your std::vector by passing the iterator to the beginning and the iterator to the end of the desired part to extract in the constructor's parameters.
For example:
int * buffer = readAll("path/to/my/file"); // Let's assume the readAll() function exists for this example
// If you want to extract from element 5 to element 9 of the buffer
std::vector<int> vec(buffer+5, buffer+9);
If the std::vector already exists, you can use the assign() member function as you already did in myfunc2():
vec.assign(buffer+5, buffer+9);
Of course in both cases, you have to ensure that you are not trying to access an out of bounds element when accessing the buffer.
The problem is that you cannot take the address of data(), since it is only a temporary copy of the pointer, so writing to a pointer to it makes not that much sense. And that is good that way. You DO NOT want to pass data() to this function since it would overwrite the pointer with a new array and that would break the vector. You can remove one * from the function and only assign to it and not allocate the memory there. This will work, but make sure to allocate the memory in the caller (with resize, just reserve will result un undefined behavior, since data() is only a pointer to the beginning of the valid range [data(), data() + size()). The range [data(), data() + capacity ()) is not necessary valid.

Retrieval of value back from C++ map<T, const T&> returns same object

I created a map of type map<T, const T&>. For current example purpose, let say T is:
class Bar {
public:
Bar(int x) {this->x = x;}
int x;
};
Next I create a map and insert Bar keyed with some integers.
Bar bs[] = {Bar(1), Bar(2), Bar(3)};
map<int, const Bar&> my_map;
for (int i = 0; i < 3; i++) {
const Bar &b = bs[i];
cout << "Setting map." << i
<< " with x = " << b.x << endl ;
my_map.insert(std::make_pair(i, b));
}
So far everything looks good, and b.x prints the values 1; 2; 3 as expected. Next we retrieve these values back.
for (int i = 0; i < 3; i++) {
auto iter = my_map.find(i);
if (iter == my_map.end()) {
cout << "Not found!" << endl;
continue;
}
cout << "map." << i << " = " << iter->second.x << endl;
}
The output prints the last value each time as shown below.
// map.0 = 3
// map.1 = 3
// map.2 = 3
And that's what is confusing to me, as I expect 1; 2; 3. If I replace value type of map with just const Bar it gives 1; 2; 3. I've been trying to make sense out of it, but so far it just looks like undefined behaviour to me. The wildest explanation I can imagine is that &b is like a box storing pointer to the object, and the box ends up being shared across loop, and make_pair uses &b as a box value than like a pointer/reference (and hence explains the last value being printed).
Edit: I understand it may not be good idea to use map like this, but I'm curious why this is happening than what should I be using instead. As in semantically, what did I miss when I wrote this and why it went through compiler, or why compiler made whatever assumption it made.
Edit: Example on repl.it running the code: https://repl.it/repls/IgnorantExhaustedBluejay
Essentially the same problem as here: How can I have a pair with reference inside vector?
Your call to std::make_pair creates a temporary std::pair object that does not have a reference as its second member. The second member of the pair is a regular value of type Bar. Meanwhile, your map stores references. The reference gets bound to the second member of the temporary created by std::make_pair. Later the temporary gets destroyed. The reference becomes dangling.
Each temporary on each iteration of the cycle is apparently created at the same location in memory. So, all these dangling references in your map refer to the same location in memory. Which just happens to hold the residual value of 3 at the time of printing. That explains the output.
A map with raw references is not a very good idea. But if you want to somehow force it to work with raw references, stop using std::make_pair. Instead, manually construct a proper std::pair, making sure to explicitly specify the proper types
my_map.insert(std::pair<const int, const Bar &b>(i, b));
Or you can keep using std::make_pair as follows
my_map.insert(std::make_pair(i, std::cref(b)));
But switching entirely to std::reference_wrapper and std::cref is a better idea.
P.S. BTW, in C++17 mode GCC refuses to compile the code with raw references. C++14 mode does compile it.
I wasn't even aware that it's possible to have a map of references
You should probably simply store the object you want directly :
map<int, Bar> my_map;
If you want the "Bar"s objects to live outside the map, you should use pointers instead of references. Just be sure you don't destruct the Bar objects without removing them from the map :
map<int, Bar*> my_map;
my_map[2] = &bs[0];
and then:
int x = my_map[2]->x;
Edit
I think the map is holding a reference to the temporary pair. You can see this in debug if you extract the creation of the pair :
auto tempPair = std::make_pair(i, b);
my_map.insert(tempPair);
Then after adding bs[0] if we run the creation of the pair, the value of my_map[0] change even before adding the second one:
This makes it work:
my_map.insert(std::make_pair(i, std::reference_wrapper<const Bar>(b)));

Using vector::back() to modify vector element

I have the following struct:
#include <string>
#include <vector>
struct A {
std::string name;
int id;
};
And a vector containing A elements:
std::vector<A> a_vector;
I am trying to append an element to the vector and change its values using the following:
void test()
{
A a;
get_a(a);
//Up to this point I thought modifying this a object would mean modifying the back element of the vector. But it doesn't work as planned, doing this:
a.id = 2; //Doesn't modify the id of the element in the vector.
}
where get_a is defined as : (The code is simplified, in the real one I really need to pass a as argument and not get it as return)
void get_a(A& a) //This function normally assigns a in different ways
{
a_vector.emplace_back();
a = a_vector.back();
}
How can I do to have the a element be the same as the one in the vector? Do I really have to use pointers?
A a;
a = a_vector.back();
Here you're copy-assigning a_vector.back() to a. This is not a reference, so modifying a will not modify the element inside the vector.
You want this instead:
A& a = a_vector.back();
If you cannot immediately initialize your reference with a_vector.back(), consider using a pointer...
A* a;
// ...
a = &a_vector.back();
// ...
something(*a);
...or an index:
std::size_t a_idx;
// ...
a_idx = a_vector.size() - 1;
// ...
something(a_vector[a_idx]);
The pointer will work fine if you know that the vector won't get resized. If the vector resize, iterators and pointers will be invalidated.
The index will work fine even if the vector gets resized, as long as the elements are not removed/shifted around.
You need a reference to the object:
auto& a = a_vector.back();
Or, in a more compact manner:
a_vector.back().id = 2;
You're holding a copy, not the original object. That is why the object in vector does not get modified.
Answer to edited question: references can be assigned only during declaration. What you want is probably std::reference_wrapper, but anyway, please don't use it unless you have to.

When appending one vector to another, why is moving the elements any cheaper than copying them?

Let's say that I have two vectors, src and dst and I want to append src to the end of dst.
I noticed that most answers regarding this task are recommending this:
dst.insert(dst.end(),
std::make_move_iterator(src.begin()),
std::make_move_iterator(src.end()));
Over this:
dst.insert(dst.end(), src.begin(), src.end());
As far as I know, pushing (inserting) elements to a vector requires allocating space for the inserted elements at the end of the vector in both cases to ensure memory contiguity, and I'm assuming that the copy and move cost is the same in this case.
Moving the objects will make them immediately destroy-able, is that the only benefit of doing so, or is there something else I'm missing?
edit:
Can you explain that in those two cases:
The vectors contain plain data, ex: int.
The vectors contain class objects.
Indeed, if copying and moving an element costs the same (is in your example with elements of type int), then there is no difference.
Moving only makes a difference for elements which themselves store their data on the heap, i.e. use allocated memory (for example if the elements are std::string or std::vector<something>). In this case, moving or copying the elements makes a (potentially huge) difference (provided the move constructor and operator=(value_type&&) are properly implemented/enabled), since a move merely copies the pointer to the allocated memory, while a copy is deep: it allocates new memory and copies all data, including recursive deep copies if applicable.
As to the costs associated with the data stored in std::vector, there are some costs if the appended elements exceed capacity. In this case, the whole vector will be resized, including moving all its elements. The reason for this is that std::vector, by specification, stores all its elements in a single array. If appending containers is a frequent operation in your code, you may want to consider other containers, such as std::list or std::deque.
I'm assuming that the copy and move cost is the same.
You assume wrong.
This has nothing to do with the vector nor the insertion. This has to do with the relative cost difference between the constructor
ClassT::ClassT(const ClassT& orig);
and
ClassT::ClassT(ClassT&& orig);
You can always find a move constructor which is cheaper or equal to the copy constructor.
A constructor is called a 'move constructor' when it takes an rvalue
reference as a parameter. It is not obligated to move anything, the
class is not required to have a resource to be moved and a 'move
constructor' may not be able to move a resource as in the allowable
(but maybe not sensible) case where the parameter is a const rvalue
reference (const T&&).
ClassT could perform the exact same operations as the copy constructor for instance. Or ClassT could perform a better move construction. Which means if you don't need the original instances of the objects in src when inserting in dst, you should use a move operation.
You already have your answer. But I think a short program can illustrate it well:
#include <iostream>
#include <utility>
#include <vector>
#include <iterator>
struct expansive {
static unsigned instances;
static unsigned copies;
static unsigned assignments;
expansive() { ++instances; }
expansive(expansive const&) { ++copies; ++instances; }
expansive& operator=(expansive const&) { ++assignments; return *this; }
};
unsigned expansive::instances = 0;
unsigned expansive::copies = 0;
unsigned expansive::assignments = 0;
struct handle {
expansive *h;
handle() : h(new expansive) { }
~handle() { delete h; }
handle(handle const& other) : h(new expansive(*other.h)) { }
handle(handle&& other) : h(other.h) { other.h = nullptr; }
handle& operator=(handle const& other) { *h = *other.h; return *this; }
handle& operator=(handle&& other) { std::swap(h, other.h); return *this; }
};
int main() {
{
std::vector<handle> v1(10), v2(10);
v1.insert(end(v1), begin(v2), end(v2));
std::cout << "When copying there were "
<< expansive::instances << " instances of the object with "
<< expansive::copies << " copies and "
<< expansive::assignments << " assignments made." << std::endl;
}
expansive::instances = expansive::copies = expansive::assignments = 0;
{
std::vector<handle> v1(10), v2(10);
v1.insert(end(v1), std::make_move_iterator(begin(v2)),
std::make_move_iterator(end(v2)));
std::cout << "When moving there were "
<< expansive::instances << " instances of the object with "
<< expansive::copies << " copies and "
<< expansive::assignments << " assignments made.\n";
}
return 0;
}
expansive models a resource that's really costly to copy (imagine opening file handles, network connections, etc). That's the resource managed by handle. To maintain programs correctness, handle must still preform the costly copying when it itself is being copied.
Now, when the program is run, it produces the following output:
When copying there were 30 instances of the object with 10 copies and
0 assignments made. When moving there were 20 instances of the
object with 0 copies and 0 assignments made.
What does it mean? It means, that if we only want to transfer the handles to another container, we have to do some really expansive work along the way (linear in the number of resources we try to transfer). Move semantics come to our rescue here.
By using a move_iterator, the actual costly resource is being transferred directly, rather than being replicated superfluously. That can translate to a massive boost in performance.

How to index into C++ shared_ptr/unique_ptr array?

As inspired by Demo of shared ptr array
I got the first two lines to work:
std::shared_ptr<string> sp( new string[3], []( string *p ) { delete[] p; } );
*sp = "john";
auto p = &(* sp);
++p = new string("Paul");
++p = new string("Mary");
for(auto q = &(*sp); q <= p; q++) cout << *q << endl;
(1) Can someone show me how to access subsequent elements of my array and print them out with a for loop?
My for loop does not print anything with MSVC V19 and and g++ v4.9 prints "john" but not "Paul" and "Mary" and then gives me a segmentation fault.
Now after some more google/bing searching I found some discussions suggesting I should use unique_ptr if I are not sharing it.
So I got to experimenting some more and this works:
const int len=3;
std::unique_ptr<string[]> up( new string[len] ); // this will correctly call delete[]
up[0] = "john";
up[1] = "paul";
up[2] = "mary";
for(int ii = 0; ii < len; ++ii) cout << up[ii] << endl;
(2) Is there a way to print the array up without hard coding the length in a constant? I was hoping that there was a way to make the new C++ for loop syntax work but it appears only work on std::array and std::vector.
(3) Is there is an easier way to initialize this array? Perhaps with an initializer list?
To access subsequent elements use the shared_ptr::get member function
std::shared_ptr<string> sp( new string[3], std::default_delete<string[]>() );
sp.get()[0] = "John";
sp.get()[1] = "Paul";
sp.get()[2] = "Mary";
for(auto q = sp.get(); q < sp.get() + 3; q++) cout << *q << endl;
The problem with dereferencing the shared_ptr is that it'll return a string&, but you want to get access to the underlying pointer in order to be able to index to next element.
You can also initialize the shared_ptr array upon construction using list initialization
std::shared_ptr<string> sp( new string[3]{"John", "Paul", "Mary"},
std::default_delete<string[]>() );
As for unique_ptr, as you've noted, there exists a partial specialization that'll delete array types correctly. However, it does not store the length of the array, you'll need to store it separately and pass it around along with the unique_ptr. For one-line initialization you can use the same syntax as above.
std::unique_ptr<string[]> up( new string[len]{"John", "Paul", "Mary"} );
However, neither the shared_ptr nor the unique_ptr can be used with ranged-based for loops. The specification of a range for requires the operand to either be an array, or it must have begin() and end() member functions, or the begin and end iterators for the range must be obtainable by ADL using the expressions begin(__range) and end(__range) respectively. None of these conditions are satisfied by either the shared_ptr or the unique_ptr containing an array.
Unless you have a good reason not to, you should just use std::vector<std::string>, that'll save you the trouble of tracking array length separately. std::array<std::string, N>> is also another option if you know the length of the array at compile time.
With C++17, operator[] is defined for shared_ptr<T[]> (link). Before that it was there for unique_ptr, but not for shared_ptr.
So, the following code will work.
std::shared_ptr<string[]> sp(new string[3]);
sp[0] = "John";
sp[1] = "Paul";
sp[2] = "Mary";
for(int i =0; i< 3; i++)
{
cout<<sp[i]<<"\n";
}
Note that it is define for shared_ptr<T[]>, and not for shared_ptr<T>, i.e. when you have a dynamically allocated array. Also I have removed your custom deleter, as your's is similar to the default one which the compiler will use, you are however free to add it.
For your second query - being able to use new C++ for loop (range based for loop), you cannot use this smart pointer directly, since range based for loop works only on containers which have begin(), end(), operator++, operator*() , and, operator!= defined (link).
The simplest way is to use std::vector with pre-allocated size. eg. vector<T> sp(3), or vector<T> sp; sp.reserve(3). This will work as good as your smart pointers' approach. To get underlying memory you can use vector<T>::data() eg. sp.data(). This will give you a dynamic C array of 3 elements (n elements in general).
How to index into C++ shared_ptr/unique_ptr array?
You've already shown the code for unique_ptr.
This works for shared_ptr.
std::shared_ptr<string> sp( new string[3], []( string *p ) { delete[] p; } );
sp.get()[0] = "string 1";
sp.get()[1] = "string 2";
sp.get()[2] = "string 3";