Consider a fixed array of integers in C++:
1 |
int array[5]; |
If we want to initialize this array with values, we can do so directly via the initializer list syntax:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { int array[] { 5, 4, 3, 2, 1 }; // initializer list for (auto i : array) std::cout << i << ' '; return 0; } |
This prints:
5 4 3 2 1
This also works for dynamically allocated arrays:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> int main() { auto *array{ new int[5]{ 5, 4, 3, 2, 1 } }; // initializer list for (int count{ 0 }; count < 5; ++count) std::cout << array[count] << ' '; delete[] array; return 0; } |
In the previous lesson, we introduced the concept of container classes, and showed an example of an IntArray class that holds an array of integers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
#include <cassert> // for assert() #include <iostream> class IntArray { private: int m_length{}; int *m_data{}; public: IntArray() = default; IntArray(int length): m_length{ length }, m_data{ new int[length]{} } { } ~IntArray() { delete[] m_data; // we don't need to set m_data to null or m_length to 0 here, since the object will be destroyed immediately after this function anyway } int& operator[](int index) { assert(index >= 0 && index < m_length); return m_data[index]; } int getLength() const { return m_length; } }; int main() { // What happens if we try to use an initializer list with this container class? IntArray array { 5, 4, 3, 2, 1 }; // this line doesn't compile for (int count{ 0 }; count < 5; ++count) std::cout << array[count] << ' '; return 0; } |
This code won’t compile, because the IntArray class doesn’t have a constructor that knows what to do with an initializer list. As a result, we’re left initializing our array elements individually:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int main() { IntArray array(5); array[0] = 5; array[1] = 4; array[2] = 3; array[3] = 2; array[4] = 1; for (int count{ 0 }; count < 5; ++count) std::cout << array[count] << ' '; return 0; } |
That’s not so great.
Class initialization using std::initializer_list
When a compiler sees an initializer list, it automatically converts it into an object of type std::initializer_list. Therefore, if we create a constructor that takes a std::initializer_list parameter, we can create objects using the initializer list as an input.
std::initializer_list lives in the <initializer_list> header.
There are a few things to know about std::initializer_list. Much like std::array or std::vector, you have to tell std::initializer_list what type of data the list holds using angled brackets, unless you initialize the std::initializer_list right away. Therefore, you’ll almost never see a plain std::initializer_list. Instead, you’ll see something like std::initializer_list<int> or std::initializer_list<std::string>.
Second, std::initializer_list has a (misnamed) size() function which returns the number of elements in the list. This is useful when we need to know the length of the list passed in.
Let’s take a look at updating our IntArray class with a constructor that takes a std::initializer_list.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
#include <cassert> // for assert() #include <initializer_list> // for std::initializer_list #include <iostream> class IntArray { private: int m_length{}; int *m_data{}; public: IntArray() = default; IntArray(int length) : m_length{ length }, m_data{ new int[length]{} } { } IntArray(std::initializer_list<int> list) : // allow IntArray to be initialized via list initialization IntArray(static_cast<int>(list.size())) // use delegating constructor to set up initial array { // Now initialize our array from the list int count{ 0 }; for (auto element : list) { m_data[count] = element; ++count; } } ~IntArray() { delete[] m_data; // we don't need to set m_data to null or m_length to 0 here, since the object will be destroyed immediately after this function anyway } IntArray(const IntArray&) = delete; // to avoid shallow copies IntArray& operator=(const IntArray& list) = delete; // to avoid shallow copies int& operator[](int index) { assert(index >= 0 && index < m_length); return m_data[index]; } int getLength() const { return m_length; } }; int main() { IntArray array{ 5, 4, 3, 2, 1 }; // initializer list for (int count{ 0 }; count < array.getLength(); ++count) std::cout << array[count] << ' '; return 0; } |
This produces the expected result:
5 4 3 2 1
It works! Now, let’s explore this in more detail.
Here’s our IntArray constructor that takes a std::initializer_list<int>.
1 2 3 4 5 6 7 8 9 10 11 |
IntArray(std::initializer_list<int> list): // allow IntArray to be initialized via list initialization IntArray(static_cast<int>(list.size())) // use delegating constructor to set up initial array { // Now initialize our array from the list int count{ 0 }; for (int element : list) { m_data[count] = element; ++count; } } |
On line 1: As noted above, we have to use angled brackets to denote what type of element we expect inside the list. In this case, because this is an IntArray, we’d expect the list to be filled with int. Note that we don’t pass the list by const reference. Much like std::string_view, std::initializer_list is very lightweight and copies tend to be cheaper than an indirection.
On line 2: We delegate allocating memory for the IntArray to the other constructor via a delegating constructor (to reduce redundant code). This other constructor needs to know the length of the array, so we pass it list.size(), which contains the number of elements in the list. Note that list.size() returns a size_t (which is unsigned) so we need to cast to a signed int here. We use direct initialization, rather than brace initialization, because brace initialization prefers list constructors. Although the constructor would get resolved correctly, it’s safer to use direct initialization to initialize classes with list constructors if we don’t want to use the list constructor.
The body of the constructor is reserved for copying the elements from the list into our IntArray class. For some inexplicable reason, std::initializer_list does not provide access to the elements of the list via subscripting (operator[]). The omission has been noted many times to the standards committee and never addressed.
However, there are easy ways to work around the lack of subscripts. The easiest way is to use a for-each loop here. The for-each loops steps through each element of the initialization list, and we can manually copy the elements into our internal array.
One caveat: Initializer lists will always favor a matching initializer_list constructor over other potentially matching constructors. Thus, this variable definition:
1 |
IntArray array { 5 }; |
would match to IntArray(std::initializer_list<int>), not IntArray(int). If you want to match to IntArray(int) once a list constructor has been defined, you’ll need to use copy initialization or direct initialization. The same happens to std::vector and other container classes that have both a list constructor and a constructor with a similar type of parameter
1 2 |
std::vector<int> array(5); // Calls std::vector::vector(std::vector::size_type), 5 value-initialized elements: 0 0 0 0 0 std::vector<int> array{ 5 }; // Calls std::vector::vector(std::initializer_list<int>), 1 element: 5 |
Class assignment using std::initializer_list
You can also use std::initializer_list to assign new values to a class by overloading the assignment operator to take a std::initializer_list parameter. This works analogously to the above. We’ll show an example of how to do this in the quiz solution below.
Note that if you implement a constructor that takes a std::initializer_list, you should ensure you do at least one of the following:
- Provide an overloaded list assignment operator
- Provide a proper deep-copying copy assignment operator
Here’s why: consider the above class (which doesn’t have an overloaded list assignment or a copy assignment), along with following statement:
1 |
array = { 1, 3, 5, 7, 9, 11 }; // overwrite the elements of array with the elements from the list |
First, the compiler will note that an assignment function taking a std::initializer_list doesn’t exist. Next it will look for other assignment functions it could use, and discover the implicitly provided copy assignment operator. However, this function can only be used if it can convert the initializer list into an IntArray. Because { 1, 3, 5, 7, 9, 11 } is a std::initializer_list, the compiler will use the list constructor to convert the initializer list into a temporary IntArray. Then it will call the implicit assignment operator, which will shallow copy the temporary IntArray into our array object.
At this point, both the temporary IntArray’s m_data and array->m_data point to the same address (due to the shallow copy). You can already see where this is going.
At the end of the assignment statement, the temporary IntArray is destroyed. That calls the destructor, which deletes the temporary IntArray’s m_data. This leaves our array variable with a hanging m_data pointer. When you try to use array->m_data for any purpose (including when array goes out of scope and the destructor goes to delete m_data), you’ll get undefined results (and probably a crash).
Rule
If you provide list construction, it’s a good idea to provide list assignment as well.
Summary
Implementing a constructor that takes a std::initializer_list parameter allows us to use list initialization with our custom classes. We can also use std::initializer_list to implement other functions that need to use an initializer list, such as an assignment operator.
Quiz time
Question #1
Using the IntArray class above, implement an overloaded assignment operator that takes an initializer list.
The following code should run:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
int main() { IntArray array { 5, 4, 3, 2, 1 }; // initializer list for (int count{ 0 }; count < array.getLength(); ++count) std::cout << array[count] << ' '; std::cout << '\n'; array = { 1, 3, 5, 7, 9, 11 }; for (int count{ 0 }; count < array.getLength(); ++count) std::cout << array[count] << ' '; std::cout << '\n'; return 0; } |
This should print:
5 4 3 2 1 1 3 5 7 9 11
![]() |
![]() |
![]() |
Does this code correct for copy assignment operator?
Yes, this code is correct
>>At the end of the assignment statement, the temporary IntArray is destroyed.
Does that happen because temporary IntArray defined inside implicit copy assignment operator function, goes out of scope after assignment statement finishes executing?
The temporary is by the caller during the assignment and then passed to `operator=`. Temporaries don't have a scope. The temporary dies when the assignment is done.
>>An object of type std::initializer_list<T> is a lightweight proxy object that provides access to an array of objects of type const T.
https://en.cppreference.com/w/cpp/utility/initializer_list
According to cppreference, does the sentence mentioned above mean that 'list' (std::initializer_list<int> list) (in our example) is a lightweight proxy object that provides access to an array of objects of int?
>> We use direct initialization, rather than brace initialization, because brace initialization prefers list constructors. Although the constructor would get resolved correctly, it’s safer to use direct initialization to initialize classes with list constructors if we don’t want to use the list constructor.
I wasn't able to process those sentences. Which part of the code does it talk about? Could you clarify more?
Yes, that's what cppreference means. "T" is a very common name for arbitrary types. We'll talk about about that later (Templates).
We're using direct initialization here. If instead we used
It would infinite recursively call the list constructor until the program runs out of memory.
I need help please (with explanation or link ) to understand how std::initializer_list is implemented ( how it can knows its arguments number exactly ) . thank you :)
`std::initializer_list` requires compiler support, it can't be implemented with just C++. However I think what you're searching for are template parameter packs. If you don't know about templates yet, this will be tricky to understand, so you might want to finish the lessons before returning to this
Those ... are real, I'm not leaving anything out, this is complete code.
thank you so much nascardriver. exactly what I was confusing about, you explained :) to be honest, you never let any thing pass with even a bit of ambiguous. Thank you :)
I need to thank you for this course. Thank you for making it free and responding to our queries too. I was apprehensive about learning c++ but after referencing learncpp for the past 6 months I'm enjoying learning c++.
I'm a bit confused by this statement.
"Because { 1, 3, 5, 7, 9, 11 } is a std::initializer_list, the compiler will use the list constructor to convert the initializer list into a temporary IntArray. Then it will call the implicit assignment operator, which will shallow copy the temporary IntArray into our array object."
Why would it be a "temporary" IntArray? and why would it call the assignment operator again?
There's an equals sign there and it's not an initialization, so it's an assignment. `IntArray` doesn't have an assignment operator with a `std::initializer_list` on the right-hand-side, but it as an assignment operator with and `IntArray` on the right-hand-side. It also has a constructor that takes a `std::initializer_list`. So the compiler uses the constructor followed by the assignment operator.
Is this okay if I use void in 'void operator= (std::initializer_list<int> list)' instead of 'IntArray&' and 'return *this;'??
Sorry for asking a quite dumb question!! Now I remember that we should use 'IntArray&' instead in order to continue using an IntArray in the chaining cases :v
My quiz answer, a simple way to provide both the copy constructor and list initializer via overloaded assignment operator. Using '*this = arr' and 'reallocate' based on comments by other people.
good job :)
Why I use the default keyword in the line
?
It tells the compiler to generate a default constructor, so that we can do
The `std::initializer_list` constructor alone would be enough to do this, but it'd do unnecessary work.
I implemented the quiz like this:
I used the reallocate() function from the previous lesson.
I hope this is a proper/correct implementation.
I compiled it and the code produced correct outputs.
Feedback would be appreciated!
Thank you for these lessons!
That's a proper implementation :) If you already have the code in a function, there's no reason not to use it.
Can you explain this in little more detail with some reasoning/examples.
On line 1: As noted above, we have to use angled brackets to denote what type of element we expect inside the list. In this case, because this is an IntArray, we’d expect the list to be filled with int. Note that we don’t pass the list by const reference. Much like std::string_view, std::initializer_list is very lightweight and copies tend to be cheaper than an indirection.
On line 2: We delegate allocating memory for the IntArray to the other constructor via a delegating constructor (to reduce redundant code). This other constructor needs to know the length of the array, so we pass it list.size(), which contains the number of elements in the list. Note that list.size() returns a size_t (which is unsigned) so we need to cast to a signed int here. We use direct initialization, rather than brace initialization, because brace initialization prefers list constructors. Although the constructor would get resolved correctly, it’s safer to use direct initialization to initialize classes with list constructors if we don’t want to use the list constructor.
You quoted a lot of text there. What is it that you don't understand?
Point 1, I read again and saw comments and understood.
Point 2: This line:
We use direct initialization, rather than brace initialization because brace initialization prefers list constructors. Although the constructor would get resolved correctly, it’s safer to use direct initialization to initialize classes with list constructors if we don’t want to use the list constructor.
What is list constructor? Is it constructor delegation or it is just other name for member initialization list?
I will only be able to question the next line once I understand what is this in the first place.
Do you have some notifier for the answers you post on our comments? For instance, email?
I didn't realise I had answers on my comments!
A list construct is a constructor that takes a `std::initializer_list`. You receive email notifications when someone replies to your comment. Check your spam folder.
We use direct initialization, rather than brace initialization because brace initialization prefers list constructors.
If brace initialisation prefers list constructors than why do you use direct initilisation?
In the next line you counter brace by saying direct initialisation is safer. Even the next part of this line is confusing.
And I checked my spam, it wasn't there as well!
`IntArray` has these constructors
This causes list initialization and direct initialization to have different effects
In the example, we want to call `IntArray(int)`, so we need to use direct initialization.
Seeing that you use a common email provider, which works for others, I don't think the error is on our end. I told Alex about your notification problem for further investigation.
Understood thanks.
>I told Alex about your notification problem for further investigation.
thanks!
Everything is normal on our side. If you don't have any filters that remove the notifications, I don't know what's wrong.
I think this is a good point to ask a few things I wondered about.
Do "brace initialization", "uniform initialization" and "list initialization" all refer to the same thing (and if so, what else falls into that category)?
Why is the compiler able to cast anything that is a comma separated list in braces into a std::initializer_list even though we never told him what that is by #including the relevant header? I see why this works for certain user defined types that included the header. But why does it work for `int i{ 0 };` and similar?
If the compiler magically knows the std namespace and the initializer_list type within it without the need of including it, how can I know what names/types the compiler can see at a certain point? For example in python there is a 'dir()' function that lists the names accessible from a certain object/module (and if you didn't give anything it lists the names in the scope).
If the compiler magically knows about stuff in the standard library, can I use C++ without it using the standard library under the hood?
I understand if the second last points are too far fetched to answer them here. I guess the best thing would be if I read into the details of the compilation process/environment.
> "brace initialization", "uniform initialization" and "list initialization"
The marked part is a "braced-init-list". We're using the "braced-init-list" to to initialize an object (I don't think there's another use to them). That makes it an "initializer list". The whole thing is called "list-initialization".
Those are the official names from the standard (9.4.4 dcl.init.list).
It's also called "brace initialization", because there are braces. The term "List initialization" can be confusing, because often there's only 1 element in the list.
It's also called "uniform initialization", because it's universal (Works almost everywhere).
`std::initializer_list` is a different story. Initializer lists can exists without `std::initializer_list` (Confusing naming).
> Why is the compiler able to cast anything that is a comma separated list in braces into a std::initializer_list
You can't use `std::initializer_list` without #including <initializer_list>.
`auto` (That includes the for-range-initializer) gets special treatment from the standard. It tries to be a `std::initializer_list`. This behavior can't be created in code (ie. you can't create a type where `auto` does this, it only works for `std::initializer_list`, because the standard says so).
> how can I know what names/types the compiler can see at a certain point? (But of course there is no declaration of `typeid` in , because `typeid` is a keyword).
You can only use what you include. If you try to use something but you're missing the include, you won't be able to compile. The only weird thing I know of is `typeid`. Although it's a keyword, you're not allowed to use it without including
> If the compiler magically knows about stuff in the standard library, can I use C++ without it using the standard library under the hood?
I don't know if the standard enforces the availability of the standard library. There are compilers where you don't have a standard library, eg. when developing for Arduino.
Thanks, that cleared the ambiguities. Here's an example I thought of:
> One caveat: [...] Thus, this variable definition: IntArray array { 5 };
would match to IntArray(const std::initializer_list<int> &), not IntArray(int).
The initializer list constructor was explicitly declared to copy the list by value. (why?)
`std::initializer_list` is very fast to copy (Because it doesn't store the elements directly). So fast indeed, that it can be faster to copy an `std::initializer_list` than passing it by reference (Because the reference requires an indirection). See sAy's comment https://www.learncpp.com/cpp-tutorial/10-7-stdinitializer_list/comment-page-2/#comment-457145
The sentence you quoted was missing during the update, it should say "IntArray(std::initializer_list)", not "IntArray(const std::initializer_list &)". I updated it in the lesson, thanks!
According to this StackOverflow question (https://stackoverflow.com/questions/17803475/why-is-stdinitializer-list-often-passed-by-value), it seems that std::initializer_list is usually implemented as a pair of pointers. cppreference (https://en.cppreference.com/w/cpp/utility/initializer_list) also says that an object of this type is lightweight. Therefore, passing std::initializer_list by const value is probably almost as efficient as passing by const reference, and it can save a dereferencing (negligible, though).
Thanks for bringing this to our attention! I updated the lesson to pass `std::initializer_list` by value. This change might affect lessons after this. If you see `std::initializer_list` being passed by reference in another lesson, feel free to point it out :)
hello! this question might have already been answered and if it was I'm sorry for asking again.
How do you go about implementing an initializer list for a user defined type?
This lesson shows exactly how to do that. See the function
IntArray(const std::initializer_list &list)
I know this code can have an improvement and not change if the length is the same.
Could you explain this warning?
Warning C6386 Buffer overrun while writing to 'm_data': the writable size is 'm_length*4' bytes, but '8' bytes might be written.
There's nothing wrong with your code, VS is wrong. You can disable the warning by adding /Wd6386 to your project's compile settings.
hi! In the Quiz line 43:
If the std::initializer_list is not const, the compiler says that Im refering to an explicitly deleted function. Why? I know that we dont want to change the list so it should be const, but why cant the compiler tell the deleted operator from the other without the new one being const, even though their parameters are different anyway?
can't bind to an r-value initializer list, so the constructor ignores this function.
The compiler has to find some other way of compiling
The only other way is to construct an `IntArray` from the initializer list, then use `operator=(const IntArray& list)`. That would work, but this operator is deleted.
Now I get it, thanks!
In the quiz exercise, why can't we just call the constructor that takes ::initializer_list as a parameter, instead of using another loop in the implementation of the assignment operator?
Calling the constructor would create a new object, not modify the current one. You could add another function that sets the current object from an `std::initializer_list` and call that from the constructor and assignment operator.
Thanks!
For example if we had.
This would just recopy the same numbers into the list again! I'd say that's inefficient.
There is no implicit conversion for an int array to an initializer list so (this == &list) would not work.
The function can be changed a bit to improve the efficiency.
However I was wondering, how we could do an overloading type to std::inilizer_list such that if we did,
it would work.
Self-assignments are extremely rare. We're checking for self-assignments to prevent errors, not for efficiency.
Without testing, I'd say your code is less efficient than an implementation that copies the values unconditionally. A quick test confirms my assumption, at least on my system.
For custom types, your version prevents the type from doing its own self-assignment check, which might be a lot faster than yours.
This can never work, because you're comparing pointers of different types. You can use `std::equal` to compare lists.
But isn't self-assignment considered dangerous or time wasting in assignment operators 9.14.
I know that
if (length != m_length)
pretty much prevents the object to point to a garbage address, such that it ensures an identical object's array does not get go out of the heap. Am i correct?
So comparable to what I did, what does the compiler do in it's own self-assignment check.
Also out of curiosity, would a copy and swap idiom algorithm be an good alternative?
Self-assignment is dangerous and time-wasting. You can fix the dangerous part, but not the time-wasting part. The only real way to not waste time is to not assign an object to itself in the first place.
> if (length != m_length)
This check is optional. If the old and new array have the same length, we can re-use the memory. The check could also be `if (length <= m_length)`. That way we'd waste some memory, but we don't have to re-allocate. > what does the compiler do in it's own self-assignment check
There's no self-assignment check in the compiler. Built-in types don't need self-assignment checks, because a self-assignment isn't dangerous for them.
> would a copy and swap idiom algorithm be an good alternative?
Yes. If I recall correctly, Alex wants to integrate this into the lessons.
Hmm I think I'm missing a nuance here. One prefers uniform initialization to prevent copies. But, in the std::initializer_list constructor, I'm copying the elements one by one. I can't think of a way to prevent those copies though. We have n copies here, else, without uniform init, we'd have 2n copies. Well better this than 2n I guess!
Can't I cheat by assigning references to the elements in the constructor? Lifespan extension through reference? But I think that was only for r-value const references though, so the 'cheat' wouldn't work here, unless the array itself as const).
In the solution of the quiz, when you return *this do you return pointer to the entire class?
`this` is a pointer, `*this` is a reference. If you're unsure about what `this` is, re-read lesson 8.8.
Hi!
My original answer to this quiz was:
And I get a warning "Buffer overrun while writing to 'm_data': the writable size is 'm_length*4' bytes, but '8' bytes might be written."
When I replace lines 3-5 with your answer, that warning goes away.
I'm not understanding what is relevantly different between the two. Can you tell me why that warning is occurring, and how the second piece of code fixes it?
There is no buffer overrun in your code. The warning is wrong.
Phew, I was all kinds of confused! Thanks!
"Rule: If you provide list construction, it’s a good idea to provide list assignment as well"
Why should one prefer overloading list assignment over regular assignment? The latter seems more general, isn't that's a good thing?
If you provide a list constructor but not a list assignment operator and you assign a list, you're calling the list constructor and then the copy/move constructor, which is slower than immediately calling a list assignment operator.
Good morning, how does this code seem?
Good day,
- Line 18: Initialize in the member initializer list.
- Line 22: Brace initialization.
- Line 24-29, 50+: Can be replaced with `std::copy(list.begin(), list.end(), m_data);`. This isn't covered on learncpp.
- `IntArray::getLength` should be const.
- Line 76, 82: Use single quotation marks for characters.
Hello nascar, thanks for your input.
>Line 22: Brace initialization. : do you mean the
Because if I do that, i get error saying: constructor delegates to itself
My bad, use direct initialization.
Hey, just a quick question about the quiz solution:
Would we not see undefined behavior in this section if someone passes an empty initializer list {}, where list.size() = 0? I'm just not sure what the behavior of new [] is when the size passed to new is 0.
Actually, I didn't think through the if clause inside, I answered myself here. Regardless, could you please tell me what's the return value when we call new int[0]? I would assume it's nullptr but then you're manually assigning nullptr again so I'm not sure. Thanks again!
0-sized arrays are legal when dynamically allocating arrays (But not for static arrays!).
@new will return the address of the newly allocated empty array.
Although empty arrays are not particularly useful, they won't crash.
Line 5 of your quoted code doesn't do anything, since @m_arr is a @nullptr at that point already.
Hi,
Suppose i have a class with below variables
How to Initialize both while creating the object
I tried writing the constructor as below,but getting error.
could you please tell me how this can be handled
@list1 is an @std::initializer_list. You're missing the template argument, and '100' isn't an initializer list.
You don't need an initializer list at all to make your call work.
Thanks @nascardriver
So if this work without having the std::initializer_list then in which scenario std::initializer_list is used?
If you wanted to do something like this
This can be achieved with an @std::initializer_list constructor
Thanks @nascardriver
Hi,
While studying delegating constructors(https://www.learncpp.com/cpp-tutorial/8-6-overlapping-and-delegating-constructors/), I read,
"A few additional notes about delegating constructors. First, a constructor that delegates to another constructor is not allowed to do any member initialization itself. So your constructors can delegate or initialize, but not both."
But here we are delegating from one constructor to another to allocate memory and then initializing it members in the constructor which just did a delegation.
Which part I understood wrong ??
Hi!
The constructor's body can do whatever it wants, but there can not be any initializations in the member initializer list.
Hi @nascardriver,
Understood, it's clear now.
Thanks a lot !!
Hi Alex,
Just to be clear: If the copy constructor and the assignment operator are not deleted, then they should (both) be overloaded and the initializer constructor made explicit? I've done this below:
No. If you have a constructor that takes a std::initializer_list, you should have a corresponding list-assignment or a copy assignment function. Otherwise you'll get a shallow copy if you do a list assignment.
The prior recommendation to make your std::initializer_list constructor explicit doesn't prevent this by itself, so that recommendation has been revoked, but it's a good idea to mark all single-parameter constructors as explicit anyway, so no harm there.
> No. If you have a constructor that takes a std::initializer_list, you should have a corresponding list-assignment or a copy assignment function. Otherwise you'll get a shallow copy if you do a list assignment.
But if you're too lazy to have the corresponding list-assignment or copy assignment functions, you should delete them so that they can't be used to shallow copy by accident?
Yep.
@Alex, your solution again wouldn't compile with the stricter compiler error levels you recommended in 0.11. Here is what you should change:
In the IntArray(const std::initializer_list &list) constructor, you need to call the delegating constructor with IntArray(static_cast<int>(list.size()), else you will get a -Werror warning treated as an error about a narrowing conversion which might change the sign. Thus:
Likewise in the operator=(const std::initializer_list &list) method. I solved the problem like this:
Further, I get a -Werror warning saying the class implements pointer members, but doesn't override IntArray(const IntArray&) or operator=(const IntArray&). I did this:
and the compiler was happy!
Thanks, and updated. I'm sure there are other instances of examples that were compiled before the new settings recommendations were put in place. If you find other examples that generate warnings, please point them out so I can update them. Much appreciated!
Will do as I find them! Thanks to you for the awesome tutorials!
hi Alex,
about the following code(i might be wrong):
since copying fundamental data types is faster than referencing them.
Updated. Thanks!
Hello! On the way to figuring out the quiz I had a question I couldn't quite answer. I originally had my operator overload function written like the following, which does print the expected output but also results in a runtime error because it accidentally results in the class Destructor trying to delete unallocated memory later on. I understand how to fix it and get the program to run correctly.
My question is, with the following INCORRECT code, since I delete[] m_data and free the dynamically allocated memory but forget to reallocate the m_data array via the "new" keyword, where and how is my for-each loop setting up the (1, 3, 5, 7, 9, 11) array that still prints correctly?
In a new memory address on the stack, instead of the heap as originally intended? Or is it able to still store and retrieve values in/from the same heap addresses even though they remain unallocated (which doesn't make sense to me)? I just want to make sure I understand what's really going on under the hood. Thank you!
Deallocating memory returns it back to the OS to be reallocated for another purpose. It typically doesn't clear the contents of that memory. So if you use a pointer to access the deallocated memory, the original contents of that memory may (or may not) still be there.
And using a pointer you can still assign to that memory as well apparently?
Yup, and you'll get undefined behavior.
Thanks Alex, it all makes sense now!