Consider this implementation of std::vector::reserve() from the book "The C++ Programming Language, 4th ed., Bjarne Stroustrup:
template<class T, class A>
void vector<T,A>::reserve(size_type newalloc)
{
if (newalloc<=capacity()) return;
vector_base<T,A> b {vb.alloc,newalloc}; // get new storage
// (see PS of question for details on vb data member)
T* src = elem; // ptr to the start of old storage
T* dest = b.elem; // ptr to the start of new storage
T* end = elem+size(); // past-the-end ptr to old storage
for (; src!=end; ++src, ++dest) {
new(static_cast<void*>(dest)) T{move(*src)}; // move construct
src–>~T(); // destroy
}
swap(vb,b); // install new base (see PS if needed)
} // implicitly release old space(when b goes out of scope)
Note that in the loop, for each element in vector, at least one call is made to a ctor and a dtor(possibly triggering more such calls if the element's class has bases, or if the class or its bases have data members with ctors). (In the book, for-loop is actually a separate function, but I injected it into the reserve() here for simplicity.)
Now consider my suggested alternative:
template<class T, class A>
void vector<T,A>::reserve(size_type newalloc)
{
if (newalloc<=capacity()) return;
vector_base<T,A> b {vb.alloc,newalloc}; // get new space
memcpy(b.elem, elem, sz); // copy raw memory
// (no calls to ctors or dtors)
swap(vb,b); // install new base
} // implicitly release old space(when b goes out of scope)
To me, it seems like the end result is the same, minus the calls to ctors/dtors.
Is there a situation where this alternative would fail, and if so, where is the flaw?
P.S. I don't think it is much relevant, but here are the data members of vector and vector_base classes:
// used as a data member in std::vector
template<class T, class A = allocator<T> >
struct vector_base { // memory structure for vector
A alloc; // allocator
T* elem; // start of allocation
T* space; // end of element sequence, start of space allocated for possible expansion
T* last; // end of allocated space
vector_base(const A& a, typename A::size_type n)
: alloc{a}, elem{alloc.allocate(n)}, space{elem+n}, last{elem+n} { }
~vector_base() { alloc.deallocate(elem,last–elem); } // releases storage only, no calls
// to dtors: vector's responsibility
//...
};
// std::vector
template<class T, class A = allocator<T> >
class vector {
vector_base<T,A> vb; // the data is here
void destroy_elements();
public:
//...
};
This might fail:
memcpy() will work only if you have a vector of POD.
It will fail for all other kind of objects as it doesn't respect the semantic of the objects it copies (copy construction).
Example of issues:
If the constructor of the object sets some internal pointers to internal members, your memcpy() will copy the value of the original pointer, which will not be updated correctly and continue to point to a memory region that will be released.
If the object contains a shared_ptr, the object count will become inconsistent (memcpy() will duplicate the pointer without incrementing its reference count, then swap() will make sure that the original shared pointer will be in b, which will be released, so that the shared pointer reference count will be decremented).
As pointed out by T.C in the comments, as soon as your vector stores non-POD data the memcpy() results in UB (undefined behaviour).
Related
I wonder if the range constructor of std::vector does copy the data, or does it just reference it?
Have a look at this example:
vector<int> getVector() {
int arr[10];
for(int i=0; i<10; ++i) arr[i] = i;
return vector<int>(arr, arr+10);
}
Would this cause a bug (due to handing out a reference to the stack which is destroyed later) or is it fine, since it copies the data in the constructor?
Edit #1
For clarification: I'm looking for a more or less official resource that points out, which of the following pseudo code implementations of the constructor are valid. I know the signature of the constructor is different... but, you should get the idea.
Version A (just uses the given data internally)
template<typename T>
class vector {
private:
T* data;
int size;
public:
vector<T>(T* start, T* end) {
data = start;
size = (end - start);
}
};
Version B (explicitly copies the data)
template<typename T>
class vector {
private:
T* data;
int size;
public:
vector<T>(T* start, T* end) {
for(T* it = start; it < end; ++it) push_back(*it);
}
};
When in doubt, check the reference. The answer can be derived from Complexity section, although I'd agree there is no explicit confirmation:
Complexity: Makes only N calls to the copy constructor of T (where N
is the distance between first and last) and no reallocations if
iterators first and last are of forward, bidirectional, or random
access categories. It makes order N calls to the copy constructor of T
and order logN reallocations if they are just input iterators.
Like all constructors of std::vector<int>, this copies the integers. The same holds for methods like push_back and insert
This is why std::vector actually has two template arguments. The second one is defaulted to std::allocator; it's the allocator used to allocate memory for the 10 integers (and perhaps a few more so that the vector can grow - see capacity)
[Edit]
The actual code is most like Version B, but probably similar to
template<typename T>
class vector {
private:
T* _Data = nullptr;
size_t _Capacity = 0;
size_t _Used = 0;
public:
vector<T>(T* start, T* end) {
_Used = (end-begin);
reserve(_Used); // Sets _Data, _Capacity
std::uninitialized_copy(begin, end, _Data);
}
};
The C++ standard library is specified in a somewhat strange way.
It is specified saying what each method requires and what each method guarantees. It is not specified as in "vector is a container of values that it owns", even though that is the real underlying abstraction here.
Formally, what you are doing is safe not because "the vector copies", but because none of the preconditions of any of the methods of std vector are violated in the copy of the std vector your function returns.
Similarly, the values are set to be certain ones because of the postconditions of the constructor, and then the pre and post conditions of the copy constructor and/or C++17 prvalue "elision" rules.
But trying to reason about C++ code in this way is madness.
A std::vector semantically is a regular type with value semantics that owns its own elements. Regular types can be copied, and the copies behave sane even if the original object is destroyed.
Unless you make a std::vector<std::reference_wrapper<int>> you are safe, and you are unsafe for the reference wrapper because you stored elements which are not regular value types.
The vector can not be defined as a vector of references as for example std::vector<int &>. So the code is valid. The vector does not contain references to elements of the array. It creates new elements of the type int (as the template argument of the vector) not a vector of references.
My teammates are writing a fixed-size implementation of std::vector for a safety-critical application. We're not allowed to use heap allocation, so they created a simple array wrapper like this:
template <typename T, size_t NUM_ITEMS>
class Vector
{
public:
void push_back(const T& val);
...more vector methods
private:
// Internal storage
T storage_[NUM_ITEMS];
...implementation
};
A problem we encountered with this implementation is that it requires elements present default constructors (which is not a requirement of std::vector and created porting difficulties). I decided to hack on their implementation to make it behave more like std::vector and came up with this:
template <typename T, size_t NUM_ITEMS>
class Vector
{
public:
void push_back(const T& val);
...more vector methods
private:
// Internal storage
typedef T StorageType[NUM_ITEMS];
alignas(T) char storage_[NUM_ITEMS * sizeof(T)];
// Get correctly typed array reference
StorageType& get_storage() { return reinterpret_cast<T(&)[NUM_ITEMS]>(storage_); }
const StorageType& get_storage() const { return reinterpret_cast<const T(&)[NUM_ITEMS]>(storage_); }
};
I was then able to just search and replace storage_ with get_storage() and everything worked. An example implementation of push_back might then look like:
template <typename T, size_t NUM_ITEMS>
void Vector<T, NUM_ITEMS>::push_back(const T& val)
{
get_storage()[size_++] = val;
}
In fact, it worked so easily that it got me thinking.. Is this a good/safe use of reinterpret_cast? Is the code directly above a suitable alternative to placement new, or are there risks associated with copy/move assignment to an uninitialized object?
EDIT: In response to a comment by NathanOliver, I should add that we cannot use the STL, because we cannot compile it for our target environment, nor can we certify it.
The code you've shown is only safe for POD types (Plain Old Data), where the object's representation is trivial and thus assignment to an unconstructed object is ok.
If you want this to work in all generality (which i assume you do due to using a template), then for a type T it is undefined behavior to use the object prior to construction it. That is, you must construct the object before your e.g. assignment to that location. That means you need to call the constructor explicitly on demand. The following code block demonstrates an example of this:
template <typename T, size_t NUM_ITEMS>
void Vector<T, NUM_ITEMS>::push_back(const T& val)
{
// potentially an overflow test here
// explicitly call copy constructor to create the new object in the buffer
new (reinterpret_cast<T*>(storage_) + size_) T(val);
// in case that throws, only inc the size after that succeeds
++size_;
}
The above example demonstrates placement new, which takes the form new (void*) T(args...). It calls the constructor but does not actually perform an allocation. The visual difference is the inclusion of the void* argument to operator new itself, which is the address of the object to act on and call the constructor for.
And of course when you remove an element you'll need to destroy that explicitly as well. To do this for a type T, simply call the pseudo-method ~T() on the object. Under templated context the compiler will work out what this means, either an actual destructor call, or no-op for e.g. int or double. This is demonstrated below:
template<typename T, size_t NUM_ITEMS>
void Vector<T, NUM_ITEMS>::pop_back()
{
if (size_ > 0) // safety test, you might rather this throw, idk
{
// explicitly destroy the last item and dec count
// canonically, destructors should never throw (very bad)
reinterpret_cast<T*>(storage_)[--size_].~T();
}
}
Also, I would avoid returning a refernce to an array in your get_storage() method, as it has length information and would seem to imply that all elements are valid (constructed) objects, which of course they're not. I suggest you provide methods for getting a pointer to the start of the contiguous array of constructed objects, and another method for getting the number of constructed objects. These are the .data() and .size() methods of e.g. std::vector<T>, which would make use of your class less jarring to seasoned C++ users.
Is this a good/safe use of reinterpret_cast?
Is the code directly above a suitable alternative to placement new
No. No.
or are there risks associated with copy/move assignment to an uninitialized object?
Yes. The behaviour is undefined.
Assuming memory is uninitialised, copying the vector has undefined behaviour.
No object of type T has started its lifetime at the memory location. This is super bad when T is not trivial.
The reinterpretation violates the strict aliasing rules.
First is fixed by value-initialising the storage. Or by making the vector non-copyable and non-movable.
Second is fixed by using placement new.
Third is technically fixed by using using the pointer returned by placement new, but you can avoid storing that pointer by std::laundering after reinterpreting the storage.
I dont understand the code given in Bjarne Stroustrup - Programming Chapter 19.5.6 RAII for vector:
template<typename T, typename A>
struct vector_base {
A alloc;
T* elem;
int sz;
int space;
vector_base(const A& a, int n) : alloc{a}, elem{alloc.allocate(n)}, sz{n}, space{n}{}
~vector_base() {alloc.deallocate(elem,space);}
}
//--------- vector class ------------
template<typename T, typename A = allocator<T>>
class vector : private vector_base<T,A> {
// ...
};
//--------- vector reserve ------------
template<typename T, typename A>
void vector<T,A>::reserve(int newalloc)
{
if (newalloc <= this->space) return;
vector_base<T,A> b(this->alloc,newalloc);
uninitialized_copy(b.elem, &b.elem[this->sz], this->elem);
for(int i=0; i<this->sz; ++i)
this->alloc.destroy(&this->elem[i]);
swap<vector_base<T,A>>(*this,b);
}
1) I don't understand what is the need to pass the allocator along to the new vector_base object b. The vector_base has its own allocator, why does the constructor need this->alloc?
2) I don't understand the uninitialized_copy line. It looks like we copy from b to this->elem. It should be the other way around. Is that just a mistake in the book, or am I getting it wrong?
The new vector needs to use the same allocator as the existing vector, i.e., this->alloc to make sure the reallocated memory comes from the same allocation area. If different allocators were used swap() on the last line would exchange the memory in the vector to point to memory allocated from a different allocator than that used to release the memory later.
The use of std::uninitialized_copy() is indeed wrong: it would copy from the newly created, uninitialised memory to the original values. Assuming the source and destination are appropriately put in place there is another error: if the memory of the existing vector is fully used, the expression &this->elem[this->sz] forms a reference to an object past the last element. The expression should rather read this->elem + this->sz. That is, the initialisation should be
uninitialized_copy(this->elem, this->elem + this->sz,
b.elem);
Suppose I have a MyStack class which exposes:
class MyStack {
public:
template <typename T>
T* Push() {
Reserve(sizeof(T)); // Make sure that the buffer can hold an additional sizeof(T) bytes , realloc if needed
auto prev= _top;
_top += sizeof(T);
new (prev) T();
return reinterpret_cast<T*>(prev);
}
template <typename T>
T* Pop() {
_top -= sizeof(T);
return return reinterpret_cast<T*>(_top);
}
bool Empty() const {
return _bottom == _top;
}
private:
char* _bottom;
char* _top;
};
// Assumes all stack elements have the same type
template <typename T>
void ClearStack(MyStack& stack) {
while (!stack.Empty()) {
stack.template Pop<T>()->~T();
}
}
There's a hidden bug here. Constructing T in MyStack::Push() could throw which would leave the stack buffer in an undefined state (the allocated space would contain garbage). Later, when ClearStack is called, it will attempt to reinterpret the garbage as T and invoke its destructor which could cause an access violation.
Is there a way to fix this bug by only modifying MyStack::Push()? (the limitation is because this is an external code and we prefer to make minimal changes so it's relatively easy to update the library)
I thought about changing MyStack::Push to:
T* Push() {
auto prev = _top;
T t();
Reserve(sizeof(T));
_top += sizeof(T);
reinterpret_cast<T*>(prev) = std::move(t);
return prev;
}
But it looks bad and I'm not even sure that it doesn't invoke any UB (and also forces T to have a move constructor)
Is there a better solution here to protect against throwing constructors? (preferably a small change inside MyStack::Push())
The problem here is really that your design is wrong. You're making a type that behaves a bit like std::vector, but it has no actual concept of "capacity". So when it Reserves memory, it genuinely expects that _top will point to the end of the allocated storage after this process has completed. And therefore if it doesn't, the type is in an invalid state.
Which means that, in the event of an exception, you have to undo the call to Reserve: reallocating the old size of storage and moving the stuff in that storage back1. A more vector-like implementation has 3 pointers: a pointer to the start, a pointer to the first unused byte of memory, and a pointer to past the end of the allocated storage. This way, if you Reserve but get an exception, you've simply got some extra storage lying around.
1: FYI: what you seem to be trying to do most likely will not work. Or at least, not with most user-defined C++ types. Odds are good that your Reserve call allocates new storage and does a memcpy into it and never calls the destructor on those objects (since you have no idea what type they are). Well, that's only legal for objects for which memcpy is a valid action. Namely, TriviallyCopyable types. Yet your Push function has nothing to guard it against non-TriviallyCopyable types.
Not to mention the fact that if anyone had a pointer to the old object, every Push call will invalidate that pointer. And since you don't remember the types of any objects, there's no way to reconstitute them.
How about this code:
template <typename T>
T* Push() {
Reserve(sizeof(T));
auto prev= _top;
_top += sizeof(T);
try {
new (prev) T();
return reinterpret_cast<T*>(prev);
}
catch (...) {
Unreserve(sizeof(T)); //release the memory, optional?
_top = prev;
throw;
}
}
You could use three-pointer implementation:
begin points to the first element.
end points to one past the last element
reserved points to one element past the reserved space.
begin=end=reserved(=nullptr) represents non-allocated container.
begin+1=end=reserved represents filled container with one element.
begin+1=end;begin+4=reserved represents container with one element and space for 2 more.
Then your Push method would look like:
template <typename T>
T* Push() {
if(end==reserved)
//relocate, ensure that begin<=end<reserved
new (end) T();
end+=sizeof(T);
return reinterpret_cast<T*>(end-1);
}
I am writing a collection of allocators, with the intention that they're to be used in very high performance environments, so a little bit of restricted usage (mediated by the compiler, not runtime errors) is desirable. I've been reading into the C++11 semantics of stateful allocators and how they're expected to be used by conforming containers.
I've pasted a simple allocator below which just contains a block of memory within the allocator object. In C++03, this was illegal.
template <typename T, unsigned N>
class internal_allocator {
private:
unsigned char storage[N];
std::size_t cursor;
public:
typedef T value_type;
internal_allocator() : cursor(0) {}
~internal_allocator() { }
template <typename U>
internal_allocator(const internal_allocator<U>& other) {
// FIXME: What are the semantics here?
}
T* allocate(std::size_t n) {
T* ret = static_cast<T*>(&storage[cursor]);
cursor += n * sizeof(T);
if (cursor > N)
throw std::bad_alloc("Out of objects");
return ret;
}
void deallocate(T*, std::size_t) {
// Noop!
}
};
In C++11, is this doable? What does it mean to copy a stateful allocator? Since the destination container invokes the copy constructor for all elements in the source container, must the memory inside the allocator be explicitly copied, or is default-construction enough?
This leads to the question, given performance as the ultimate goal, what are sane values for propagate_on_container_{copy,swap,move}? What does select_on_container_copy_construction return?
I'm happy to provide more details on request because this seems a rather nebulous issue -- at least to me =)
This contention arises from the definition that when a == b returns true for two instances of the same Allocator type, it is guaranteed that memory allocated with a may be deallocated with b. That seems to never be true for this allocator. The standard also states that when an allocator is copy-constructed, as in A a(b), a == b is guaranteed to return true.
The allocator requirements say that copies of an allocator must be able to free each others' memory, so it is not generally possible to store the memory inside the allocator object.
This must be valid:
using IAllocChar = internal_allocator<char, 1024>;
IAllocChar::pointer p
IAllocChar a1;
{
IAllocChar a2(a1);
p = std::allocator_traits<IAllocChar>::allocate(a2, 1);
}
std::allocator_traits<IAllocChar>::deallocate(a1, p, 1)
So you need to store the actual memory outside the allocator object (or only use it in very restricted ways that ensure the object doesn't go out of scope while anything is referring to memory it owns).
You're also going to struggle with rebinding your internal_allocator, what should the following do?
using IAllocChar = internal_allocator<char, 1024>;
using IAllocInt = std::allocator_traits<IAllocChar>::rebind_alloc<int>;
IAllocChar ac;
auto pc = ac.allocate(1); // got bored typing allocator_traits ;-)
IAllocInt ai(ac);
auto pi = ai.allocate(1);
IAllocChar(ai).deallocate(pc, 1);
IAllocInt(ac).deallocate(pi, 1);