According to N3485 §23.3.2.2:
(...) the implicit move constructor and move assignment operator for array require that T be MoveConstructible or MoveAssignable, respectively.
So, std::array supports move semantics if the type of its elements does. Great!
However, what does this really mean? I tend to picture this type as a safer version of an array providing an STL-compliant interface but, if this is true, then how can an std::array move-construct its elements? Can I do the same with an ordinary array?
However, what does this really mean?
It means that, if the element type is movable, then so is the array type.
std::array<movable, 42> move_from = {...};
std::array<movable, 42> move_to = std::move(move_from); // moves all the elements
I tend to picture this type as a safer version of an array providing an STL-compliant interface
Not really. It's a wrapper for an array, giving it the same semantics as an aggregate class - including the ability to copy and move it.
how can an std::array move-construct its elements?
In exactly the same way as any other aggregate. Its implicit move-constructor will move-construct all its members, including the elements of any member arrays.
Can I do the same with an ordinary array?
Only if you wrap it in a class type, as std::array does.
Moving a std::array is different from moving a std::vector. When moving one std::vector into another, it's (sometimes*) possible to simply re-target the internal pointers and avoid manipulating the elements at all.
With std::array, this is of course not possible - its elements have automatic storage duration, they are literally contained inside the object. However, each individual one of them can still be moved, and that's what the move operations on std::array do**.
* Assuming the allocators are compatible and don't prohibit this operation
** That's also what you get with std::vector when the buffer can't just be re-owned by the destination vector.
The default move constructor for a (non-union) class performs a member-wise move. Moving a raw array data member means moving each of the array's elements, see [class.copy]/15.
Therefore, you can move a raw array by putting it inside a class:
struct wrap
{
std::string arr[25];
};
auto w = wrap();
auto m = std::move(w); // moves the 25 `std::string`s
You can also manually invoke the move constructor of the elements, for example:
std::string a[3] = { /*...*/ };
std::string b[3] = {std::move(a[0]), std::move(a[1]), std::move(a[2])};
It is not specified if std::array contains a raw array. However, it does contain data members of the value_type, since it's guaranteed to be an aggregate. Those data members will be moved as described above when invoking the move constructor.
If the data members of a std::array are not MoveConstructible, instantiating its move constructor will fail.
You can do it using "placement new". You'll find plenty of questions on placement new already answered with many further details.
This one looks like it has a complete example:
Why is this code trying to call the copy constructor?
Related
I want an array of class objects with unique_ptr:
std::unique_ptr<MyClass[]> arr(new MyClass[n]);
MyClass has no default constructor (and in my case is not supposed to have), so I have to put it explicitly here. I cannot find out how to do it so it is syntactically correct. What is the correct way to write a unique_ptr array of class objects with explicit initialisation?
Clarification
I have a non-default constuctor for MyClass, like this:
MyClass instance(arguments);
Apart from member initialisations, there are also some calculations in the constructor. I want to create a unique_ptr array of MyClass instances and call the constructor for each of the instances. I cannot do that later since MyClass has no default constructor. Can I put (arguments) somewhere in std::unique_ptr<MyClass[]> arr(new MyClass[n])?
The answer below is based on a previous version of the question, in which the array size appeared to be a compile-time constant. If the size of the created array is not a compile-time constant, then it is impossible to pass arguments to the constructors of the elements. In that case std::vector is probably a better choice than array-std::unique_ptr.
It works the same as always for arrays, using aggregate initialization:
std::unique_ptr<MyClass[]> arr(new MyClass[]{
{...},
{...},
{...},
{...},
{...}});
where ... are replaced by the argument lists for the constructors of the five elements.
Or if you cannot use list-initialization for the elements, e.g. because that would unintentionally use a std::initializer_list constructor:
std::unique_ptr<MyClass[]> arr(new MyClass[]{
MyClass(...),
MyClass(...),
MyClass(...),
MyClass(...),
MyClass(...)});
std::make_unique would usually be preferred for creating std::unique_ptrs, but there is at the moment no overload which allows passing arguments to the constructors of the individual array elements.
If you want to pass the same argument list to each element, a simple solution given that the type is copy-constructible would be to declare one instance and then copy-construct the elements from this instance:
MyClass instance(arguments);
std::unique_ptr<MyClass[]> arr(new MyClass[]{instance, instance, instance, instance, instance);
Otherwise you could write a template function that expands these repeated items for you. (Might add example later.)
Assume a following non copyable and non movable struct X with no default constructor and with no single argument constructor:
struct X
{
X(int x, int y) { }
X(const X&) = delete;
X(X&&) = delete;
};
and a vector std::vector<pair<X,X>> v. For inserting into v one could use emplace_back if X was constructible from just one argument, since it effectively calls the constructor of std::pair<X,X>.
We could do something like this:
v.emplace_back(X(42,42),X(69,69));
but in this case a move constructor of X gets called and the latter does not compile. Since this is not possible, we have to make use of the std::piecewise_construct constructor of std::pair and call:
v.emplace_back(std::piecewise_construct, std::forward_as_tuple(42,42), std::forward_as_tuple(69,69));
I would expect this to work properly, but the vector, for some reason, is calling move ctor (or copy, if only move was deleted).
For example changing the container to be std::list, everything works just fine. Adding a < operator to X and creating a std::map<X,X> (which has pairs of X as the nodes) or std::set<std::pair<X,X>> and using emplace instead of emplace_back all seems to work. What is wrong with std::vector?
Full code snippet can be found here.
std::vector is subject to reallocation once the size reach the capacity.
when reallocating the elements into a new memory segment std::vector has to copy/move the values from the old segment and this is made by calling copy/move constructors.
if you don't need that the elements are sequential in memory you can use std::deque instead, since std::deque doesn't reallocate the elements internally.
you can't store non copyable and non moveable objects into std::vectors.
EDIT suggested by #François Andrieux
In case you still need for any reason an std::vector you may think to use a vector made using std::unique_ptr<X> as value type using std::vector<std::unique_ptr<X>>.
With this solution you still don't get a sequential order in memory of your elements, and they are keep in memory till they are still in the vector, so except in case you are forced by any reason to use std::vectors, i think the best match is still the std::deque.
Let's say I have
#include <string>
#include <vector>
using namespace std;
struct Student
{
const string name;
int grade;
Student(const string &name) : name(name) { }
};
How do I, then, keep a vector of students?
int main()
{
vector<Student> v;
// error C2582: 'operator =' function is unavailable in 'Student'
v.push_back(Student("john"));
}
Is there even a way to do this, or must I allocate all the students on the heap, and store a pointer to each of them instead?
You can't. Your type violates the "Assignable" requirement for standard containers.
ISO/IEC 14882:2003 23.1 [lib.container.requirements] / 3:
The type of objects stored in these components must meet the requirements of CopyConstructible
types (20.1.3), and the additional requirements of Assignable types.
From table 64 (Assignable requirements):
In Table 64, T is the type used to instantiate the container, t is a value of T, and u is a value of (possibly const) T.
expression: t = u; return type: T; post-condition: t is equivalent to u
In theory, a std::vector equivalent could choose to do destruction and copy construction in all cases, but that's not the contract that has been chosen. If reallocation isn't required, then using the contained type's assignment operator for things like vector::operator= and vector::assign might be significantly more efficient.
The simple answer is: you can't. If you have const member variables, then the compiler can't supply a default copy-assignment operator. However, many of the operations that std::vector provides need to make assignments, and therefore require a (public) copy-assignment operator.
Your options are:
Make name non-const.
Write your own copy-assignment operator, and think of a way to deal with "copying" a const member.
A vector often needs to move elements around. Every time a vector needs to grow when you call push_back() it reallocates memory to keep itself contiguous, and copies all the existing elements into the new space. Also if you call insert() or remove() elements must
be shifted. For vector to be able to do all that the elements must be copy-assignable, which means that the type you store in the vector must have the assignment operator defined.
Generally, if you define a class, the compiler will generate the assignment operator for that class for you. However, there are cases when the compiler is unable to do that. One of these cases is when the class has constant members (note that pointers-to-const are ok).
So, in your case, the problem is the const string name. It prevents the compiler from generating operator=(), which in turn prevents vector from compiling, even though you do not actually use assignment on its elements yourself.
One solution is to make name non-const. The other is to write your own Student::operator=(), in some way that makes sense. The third way is, as you have pointed out, to use a vector of pointers rather than a vector of objects. But then you have to handle their allocation and de-allocation.
P.S. The other case when the compiler cannot generate operator= is when your class has members that are references.
Elements of vectors must be copy-assignable, which your Student struct isn't because of the const member. Simply use string name instead of const string name.
Unless you have a specific requirement, constant members in classes are seldom useful. If you want to prevent changes to the member, make it private and add a public getter function.
And how can I write my own array class to not need a default constructor for its elements? Right now, when I do the new [] to allocate space, I need a default constructor.
std::vector does not.
How do they do this magic?
std::vector doesn't need the default constructor because it never uses it. Every time it needs to construct an element, it does it by using the copy constructor, because every time it has something to copy: either existing vector element or an element you yourself supplied for copying through a method's parameter (explicitly or implicitly, by relying on a default argument)
You can write a class like that in exactly the same way: every time you need to construct a new element in your array, require the user to supply an element for copying. In this case constructing that original element becomes user's responsibility.
Every time it appears as if std::vector "requires" a default constructor from you, it simply means that somewhere you relied on a default argument of some of the vectors methods, i.e. it was you who tried to default-construct an element, not the vector. The vector itself, again, will never try to default-construct elements.
In order to avoid the default constructor requirement during memory allocation, standard library allocates raw uninitialized memory block and then immediately copy-constructs new elements in that raw memory block (which is something new[] cannot do). This functionality is incapsulated in std::allocator class. You can use std::allocator in your code as well, meaning that the "magic" is immediately available to you too.
Note: The above applies to pre-C++11 version of C++ language specification. C++11 changed a lot of things. And these changes do create situations in which std::vector can use default constructors internally.
Also it might be worth noting that even the original C++98 specification allowed implementations to use function overloading instead of default arguments in order to implement the standard library interface. This means that formally it is possible to have a valid C++98 implementation of std::vector that uses default constructors internally.
std::vector only requires the element to have a default constructor if you use it in a way which requires the default constructor. So this code (stolen from a deleted answer) won't compile, because X does not have a default ctor:
#include <vector>
struct X
{
X(int) {}
};
int main(void)
{
std::vector<X> x(1); // vector of length 1, second argument defaults to X() !!
return 0;
}
But if you write main like this instead:
int main(void)
{
std::vector<X> x; // make empty vector
x.push_back(X(1));
return 0;
}
Then it works fine.
You could allocate a block of bytes, then use placement new to make new instance of T (your parametric type) via copy constructor (not default constructor of course) when new items are pushed to the vector's back. This will not allow to to make "a vector of N default-initialized Ts" (which std::vector can make - which is why it does need T to have a default constructor for this purpose), but you could make vectors that start empty and can have Ts pushed onto them.
For me the std::vector was requiring a default constructor for my class (say T) because I was calling resize() method of the vector, despite I was only calling the method to shrink the vector, but never to grow.
To keep the long story short, I am unable to use the container from the STL and boost library and have to create my own.
My own generic container is coded in VC++6 and I need to know how to manually allocate memory for generic types before storing it in my own container. The generic types are all struct that can contain nested struct. All struct be it nested or not will contain only primitive types like char*, int, bool etc.
For example, when you call the insert function of std::vector, internally, std::vector will automatically perform a deep cloning of the generic type before storing it.
How can I duplicate this functionality (deep cloning of generic type) in my own container?
Please provide some sample code for performing deep cloning of generic type.
The std::vector (and most std containers) just call the type's copy constructor. This may or may not "deep clone" the object, depending on what the copy constructor does.
First and for all: if you want to clone any object, all it's aggregates should be cloned, too. This means that every struct/class involved in the cloning action should implement cloning behavior.
Then: the stl uses so called value-semantics: containers will always contain their elements 'by value'. Copying means creating copies of all container elements.
So in order to achieve cloning/deep copy behavior, the copy constructors of every member of the container's element type should implement deep copy behavior. Members of pointer-to-object type should also be deep-copied (not just copying the member pointer).
Note: code is untested, probably contains tons of exception-unsafety etc... and is merely used as a shallow :) example.
struct WithPointer {
int* pint;
WithPointer( int value = 0 ) : pint( new int ) { *pint = value; }
WithPointer( const WithPointer& other ) {
pint = new int;
*pint = *other.pint;
}
~WithPointer( ) { delete pint; } // important!
}
This class can be 'deep-copied' using an stl container:
std::vector<WithPointer> v;
WithPointer wp(1);
v.push_back( wp );
std::vector<WithPointer> v2 = v;
std::vector does not perform any "deep cloning" by itself. When you insert something into std::vector, the vector allocates raw memory for the new element in the appropriate space and then creates a new element in that memory area by using direct initialization (normally through placement-new) from the element you passed to the insertion method. In other words, in order to make a copy for a class type, std::vector calls the copy-constructor of the class type. If the element type is not a class type or a class type without appropriately defined copy-constructor, the deep copy will not take place.
This is what you should do in your container type, if you want to simulate copying functionality of std::vector.