If I've got a constructor with an initialization list as such:
std::vector<int> _list;
Program::Program() : _list(0)
{
}
Does this initialize all values to 0 even when the vector resizes itself?
You seem to be misunderstanding the argument of the vector constructor that you're calling. _list(0) will initialise _list to length zero, using the default value for type int, which also happens to be zero, but which is irrelevant if the vector doesn't contain any elements. It doesn't explicitly set element values to zero. Perhaps you meant to have the constructor repeats a single value a specified number of times? If so, you need to pass the desired length as the first argument, and the value to repeat for its second argument. This construction does not affect an subsequent resizing to expand the vector, which will populate the new vector elements with the default value (or a different value if you specify one as an additional argument to vector::resize).
Here's an example to illustrate, based on your code, which first initialises the vector with the value 10 repeating for length 5, and then resizes the vector to length 10.
#include <iostream>
#include <vector>
class Program
{
public:
Program() : _list(0) { }
Program(unsigned long size, int value) : _list(size, value) { }
void ResizeList(unsigned long size)
{
_list.resize(size);
}
void PrintList() const
{
std::cout << "_list = ";
for (const auto& val : _list)
{
std::cout << val << ", ";
}
std::cout << std::endl;
}
private:
std::vector<int> _list;
};
int main()
{
Program p(5, 10);
p.PrintList();
p.ResizeList(10);
p.PrintList();
return 0;
}
Output:
_list = 10, 10, 10, 10, 10,
_list = 10, 10, 10, 10, 10, 0, 0, 0, 0, 0,
If you look at the documentation for the std::vector constructor, you'll see that for constructor (3), the one you're using, you'll see that you're constructing 0 elements of type int in-place in _list. This means that you're essentially doing nothing.
When the vector is resized, the elements that space is allocated for will be uninitialized, unless you use the resize function, in which case the elements will be initialized to their default value, or a value of your choice.
For example, if your vector were empty and you did _list.resize(10);, _list would now contain 10 elements of the default-constructed type of int, which should just be 0. If you instead did something like _list.resize(10, 5);, _list would now contain 10 5s.
I hope this helped clear things up for you. If you have any follow up questions, feel free to ask.
Related
I am confused about the output in the following program about the vec in Test. Why it's a vector with size 100 instead of 1? I thought std::vector<T> var{a} is the same as std::vector<T> var = {a}.
#include <iostream>
#include <vector>
using namespace std;
struct Value {
int a;
int b;
};
class Test {
public:
std::vector<struct Value> vec{100};
};
class Test2 {
public:
std::vector<int> vec{100};
};
int main()
{
Test test;
std::cout << "test size: " << test.vec.size() << std::endl;
Test2 test2;
std::cout << "test2 size: " << test2.vec.size();
return 0;
}
Output:
test size: 100
test2 size: 1
std::vector has a constructor with a std::initializer_list<T> argument. When using an initializer list like {100} this constructor will always take priority, if it is applicable.
For a std::vector<int> the initializer {100} is compatible with std::initializer_list<int> so that constructor will be used. It will create a vector containing the collection {100} which is a single int with the value 100.
For std::vector<Value> the initializer {100} is NOT compatible with a std::initializer_list<Value> argument. Value has no converting constructor for turning an int to a Value so you cannot construct a std::initializer_list<Value> from {100}. You can verify that this conversion is not allowed with this example. The compiler will try to take a lower priority constructor instead, and will use the constructor which initializes 100 default constructed Value.
If you add a Value::Value(int) constructor or use {{100}} as the initializer for Test2 you will find that the std::vector<Value> will now only contain a single element. In both cases, the initializer list is convertible to std::initializer_list<Value> and that constructor will now be used.
As you discovered the meaning of {100}, changes for T == int.
To answer your question briefly:
The 100 in vector<Value>{100} cannot be interpreted as a Value and therefore the size constructor takes precedence.
If you insist, {100} can be interpreted as Value, so you may need an extra curly braces, vector<Value>{ {100} }.
See the illustration here: https://godbolt.org/z/xcMT1oc5z
My advice, avoiding further discussion on legalities, is the following:
To keep the meaning across types, initialize consistently parenthesis for size-initialization and brackets for element(s), which forces you to do this:
std::vector<int> vec = std::vector<int>(100);
And in general:
std::vector<T> vec = std::vector<T>(100);
In this case 100 is always the size.
This may not be a totally helpful answer, but I decided to put a breakpoint in the class declaration for vector in the STD library.
Answer
In the definition of a vector, there are 3 ways it handles the assignment.
A struct will provide a value-construction, where as an int will be assigned as a sized range construction.
It reads std::vector<struct> vect{100}; as building a vector of length 100, while std::vector<int> vect{100}; is acting similarly vect.insert(v.end(),100);
This is based on the type of object passed in for .
For information's sake, the final option is taking a given value, and assigning it to a number of spots. So if you had 100, "x", it would put "x" into your vector 100 times.
The Journey
What I learned from this is that there's a point where your vector takes a size_type input and a _Valty&& input (which I don't know what that is yet. Will be looking it up later) and provides a construction between 3 different args.
My best guess would be that your struct is filling in for 1-args path and acts as a length declaration, while int as a native type falls into the 2-args path and acts as a value assignment.
The sizeof(Value) may == 0, while the size of an int will be 1.
Edit: I guessed 1 and 2 (or _Count == 0, and Count == 1), however I was wrong about this. It's _Count == 0 and _Count == 2. Which was very interesting.
template <class... _Valty>
_CONSTEXPR20 void _Construct_n(_CRT_GUARDOVERFLOW const size_type _Count, _Valty&&... _Val) {
// Dispatches between the three sized constructions.
// 1-arg -> value-construction, e.g. vector(5)
// 2-arg -> fill, e.g. vector(5, "meow")
// 3-arg -> sized range construction, e.g. vector{"Hello", "Fluffy", "World"}
auto& _Al = _Getal(); //////////////// For test1, _Count is 100, for test2, _Count is 1;
auto&& _Alproxy = _GET_PROXY_ALLOCATOR(_Alty, _Al);
auto& _My_data = _Mypair._Myval2;
_Container_proxy_ptr<_Alty> _Proxy(_Alproxy, _My_data);
if (_Count != 0) {
_Buy_nonzero(_Count);
_Tidy_guard<vector> _Guard{this};
// This one happens with a struct
if constexpr (sizeof...(_Val) == 0) {
_My_data._Mylast = _Uninitialized_value_construct_n(_My_data._Myfirst, _Count, _Al);
} else
if constexpr (sizeof...(_Val) == 1) {
_STL_INTERNAL_STATIC_ASSERT(is_same_v<_Valty..., const _Ty&>);
_My_data._Mylast = _Uninitialized_fill_n(_My_data._Myfirst, _Count, _Val..., _Al);
} else
// This one happens with an int
if constexpr (sizeof...(_Val) == 2) {
_My_data._Mylast = _Uninitialized_copy(_STD forward<_Valty>(_Val)..., _My_data._Myfirst, _Al);
} else {
static_assert(_Always_false<_Ty>, "Should be unreachable");
}
_Guard._Target = nullptr;
}
_Proxy._Release();
}
What's really interesting as that it appears to be happening when the Allocator reference is assigned. I'm by no means an expert in the language, but I really wanted to figure this puzzle one! Thanks for the interesting challenge!
If you've never walked through a class definition before, I would recommend trying it out.
I would like to append a initializer_list of objects returned from a function to another vector, ideally as a 1 liner like so:
int main()
{
std::vector<int> integers(0);
integers.reserve(8);
integers.insert(integers.end(), generate_4_ints()); // vector size is now 4
integers.insert(integers.end(), generate_4_ints()); // vector size is now 8
}
std::initializer_list<int> generate_4_ints()
{
int first = 1, second = 2, third = 3, fourth = 4;
return {first, second, third, fourth};
}
The only problem with the code above is that the values returned from generate_4_ints() lose their assigned value and instead return garbage, as if no copies were returned. I assume this is some peculiar behavior to do with initializer_list.
If I were to create an array with int* array = new int[10]; and fill part of the array with values, how can I check how much of the array is filled? I want to loop through and check if each value is the default value but I'm not sure what the default value of each array element is. Would it be null 0 or garbage values?
This is how to set a default value in C++ when making an array.
int array[100] = {0};
Now every element is set to 0. Without doing this every element it garbage and will be undefined behavior if used.
Not all languages are like this. Java has default values when declaring a data structure but C++ does not.
There is no default value so it's garbage.
You can't do what are you hoping to, not when the type is int.
The uninitialized elements of the array will have unpredictable values. In addition, accessing those elements is cause for undefined behavior.
You can initialize the elements of the array to a sentinel value at the time of allocation using:
int* ptr = new int[10]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
Use whatever sentinel value works for you if -1 does not.
The default value of array is indeterminate means garbage.
how can I check how much of the array is filled?
You cannot check, C/C++ has no array bounds check. You have to do it yourself.You need to keep track of the data inserted by a user. When your counter reaches the size of the array, the array is full
You can solve your problem by a more C++ way. You can create struct or class, which contain your value and bool flag. Bool flag must be set to false in default constructor and set to true in operator=. There is ready implementation of such class - boost.optional. std::optional will be in C++17.
#include <boost/optional.hpp>
#include <iostream>
int main()
{
const size_t nArr = 100;
auto pArr = new boost::optional<int>[nArr];
const size_t nInit = 30;
for (size_t i = 0; i < nInit; ++i)
{
pArr[i] = i; //initialize nInit first values of pArr
}
size_t n = 0;
for (; n < nArr; ++n)
{
if (!pArr[n].is_initialized()) break;
// or more compact form:
//if(!pArr[n]) break;
assert(*pArr[n] == n);
}
std::cout << "nInit = " << nInit << ", n = " << n << std::endl;
assert(nInit == n);
delete[] pArr;
}
It should be pointed out the default is uninitialized only for basic types like int. classes will use their defined parameterless constructor. Here is a MWE that wraps an int in such a class. OP also asked about checking the values so that is here too:
#include <iostream>
struct Element {
int Value;
Element() : Value{42} {}
};
struct Array {
Element Values[10];
};
int main() {
Array array;
for(Element element: array.Values)
std::cout << element.Value << " ";
}
Note: POD (or record) classes, which have no custom constructor, will remain uninitialised. Non-POD classes that have no parameterless constructor cannot usually be held in arrays.
I don't know if this helps but in c++17 onwards there's the std::array class that can be used. In this class you have the option to use class functions such as std::array.fill(...)
std::array<int, 10> arr;
arr.fill(-1);
cpp docs: cpp-docs
This get's you closer to the experience from other languages such as Java and Python both having Arrays.fill(arr, -1); and arr = [-1]*N respectively to fill the array with non-default values.
By default, the objects allocated by new are default initialized. This mean when you wrote:
int* array = new int[10]; //block of 10 uninitialized ints
Here, all of the 10 ints are uninitialized. That is, they have indeterminate value.
It is possible to value initialize the elements by adding an empty pair of parenthesis as shown below:
int* array = new int[10](); //block of 10 ints value initialized to 0
Here all of the 10 ints are initialized to 0.
But note that although we can use empty parentheses to value initialize the elements of an array, we cannot supply an element initializer inside the parentheses. This means for example,
int* array = new int[10](55); // INVALID
The above statement is invalid because we cannot supply an element initializer inside the parenthesize.
You can use std::fill_n instead of supplying an element initializer inside the parentheses as shown below:
int* array = new int[10]; //block of 10 uninitialized ints
std::fill_n(array, 10, 55); // all elements will now hold 55
I have a class that has a vector as one of member variables. Within the constructor, the vector capacity is reserved (class VecUser uses 'Test' object):
class Test {
public:
Test(uint32_t size) {
this->v.reserve(size);
std::cout << v.capacity() << std::endl; // this prints 'size'
}
vector<uint32_t>& getV() { return v; }
private:
vector<uint32_t> v;
};
class VecUser {
public:
VecUser() {}
private:
void func() {
Test* test = new Test(32); // This prints '32'
vector<uint32_t> v = test->getV();
std::cout << v.capacity() << std::endl; // This prints '0'
}
};
I think that the cout in the func() function has to print '32', not '0'.
But, after running it, it prints 0.
Why the reserved vector shows its capacity is 0?
This here
vector<uint32_t> v = test->getV();
Makes a copy. v isn't actually a reference, so even though you return one, it has to make a copy anyway. Because it is a copy, it doesn't need that same amount of reserved space. If you actually get the reference instead like this:
vector<uint32_t> &v = test->getV();
The output is 32 both times.
The copy-initialized v following vector<uint32_t> v = test->getV(); is a value copy of test->getV().
The C++ standard does not require the copying of the source vector's capacity following copy initialization, so the capacity of v is allowed to be any value subject to it being greater than or equal to the number of elements.
If I were to create an array with int* array = new int[10]; and fill part of the array with values, how can I check how much of the array is filled? I want to loop through and check if each value is the default value but I'm not sure what the default value of each array element is. Would it be null 0 or garbage values?
This is how to set a default value in C++ when making an array.
int array[100] = {0};
Now every element is set to 0. Without doing this every element it garbage and will be undefined behavior if used.
Not all languages are like this. Java has default values when declaring a data structure but C++ does not.
There is no default value so it's garbage.
You can't do what are you hoping to, not when the type is int.
The uninitialized elements of the array will have unpredictable values. In addition, accessing those elements is cause for undefined behavior.
You can initialize the elements of the array to a sentinel value at the time of allocation using:
int* ptr = new int[10]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
Use whatever sentinel value works for you if -1 does not.
The default value of array is indeterminate means garbage.
how can I check how much of the array is filled?
You cannot check, C/C++ has no array bounds check. You have to do it yourself.You need to keep track of the data inserted by a user. When your counter reaches the size of the array, the array is full
You can solve your problem by a more C++ way. You can create struct or class, which contain your value and bool flag. Bool flag must be set to false in default constructor and set to true in operator=. There is ready implementation of such class - boost.optional. std::optional will be in C++17.
#include <boost/optional.hpp>
#include <iostream>
int main()
{
const size_t nArr = 100;
auto pArr = new boost::optional<int>[nArr];
const size_t nInit = 30;
for (size_t i = 0; i < nInit; ++i)
{
pArr[i] = i; //initialize nInit first values of pArr
}
size_t n = 0;
for (; n < nArr; ++n)
{
if (!pArr[n].is_initialized()) break;
// or more compact form:
//if(!pArr[n]) break;
assert(*pArr[n] == n);
}
std::cout << "nInit = " << nInit << ", n = " << n << std::endl;
assert(nInit == n);
delete[] pArr;
}
It should be pointed out the default is uninitialized only for basic types like int. classes will use their defined parameterless constructor. Here is a MWE that wraps an int in such a class. OP also asked about checking the values so that is here too:
#include <iostream>
struct Element {
int Value;
Element() : Value{42} {}
};
struct Array {
Element Values[10];
};
int main() {
Array array;
for(Element element: array.Values)
std::cout << element.Value << " ";
}
Note: POD (or record) classes, which have no custom constructor, will remain uninitialised. Non-POD classes that have no parameterless constructor cannot usually be held in arrays.
I don't know if this helps but in c++17 onwards there's the std::array class that can be used. In this class you have the option to use class functions such as std::array.fill(...)
std::array<int, 10> arr;
arr.fill(-1);
cpp docs: cpp-docs
This get's you closer to the experience from other languages such as Java and Python both having Arrays.fill(arr, -1); and arr = [-1]*N respectively to fill the array with non-default values.
By default, the objects allocated by new are default initialized. This mean when you wrote:
int* array = new int[10]; //block of 10 uninitialized ints
Here, all of the 10 ints are uninitialized. That is, they have indeterminate value.
It is possible to value initialize the elements by adding an empty pair of parenthesis as shown below:
int* array = new int[10](); //block of 10 ints value initialized to 0
Here all of the 10 ints are initialized to 0.
But note that although we can use empty parentheses to value initialize the elements of an array, we cannot supply an element initializer inside the parentheses. This means for example,
int* array = new int[10](55); // INVALID
The above statement is invalid because we cannot supply an element initializer inside the parenthesize.
You can use std::fill_n instead of supplying an element initializer inside the parentheses as shown below:
int* array = new int[10]; //block of 10 uninitialized ints
std::fill_n(array, 10, 55); // all elements will now hold 55