Copy assignment definition

Pages: 123
What do you return from operator if elem = new double[a.sz]; throws?
I agree and I should take that into consideration after the current step.

but you deleted elem without assigning it to nullptr, so its now a dangling pointer.
I disagree with that, because on the following line the pointer is populated again: elem = new double[a.sz];
For operator to be truly exception safe it should probably look like this,
now you can see the meaning of temporary pointer.

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
Vector& Vector::operator=(const Vector& a) // copy assignment
{
      if (this != &a)
      {
            double* p = nullptr;

            try
            {
                  // make sure target pointer valid
                  if (a.elem == nullptr)
                        throw std::invalid_argument;

                  // create new storage, could throw
                  p = new double[a.sz];

                  // copy elements from a, now it's safe
                  for (int i=0; i!=a.sz; ++i)
                       p[i] = a.elem[i];

                  delete[] elem; // delete old elements
            }
            catch (std::bad_alloc& ex)
            {
                  delete[] elem; // delete old elements
                  elem = nullptr;
                  sz = 0;
                  // ...
            }
            catch (std::invalid_argument& ex)
            {
                 delete[] elem; // delete old elements
                 elem = nullptr;
                 sz = 0;
                 // ...
            }

             // do assignment
             elem = p;
             sz = a.sz;
      }

      // if "a" is "this" object, there is nothing to do
      return *this;
}
Last edited on
I think I got it. Just to check out:
1- If we delete elem and then assign it: elem = new double[a.sz]; since it may throw, there's a possibility we lose our target (left hand object), so we need a temp pointer. Right?
2- On line 14 if it throws, bad_allocate (line 22) catches it. Right?
3- On line 24: if we can't allocate memory by new and can't copy the right hand object (a) to the left one (&this), why destroying it!? I mean the left-hand object contains some value before the assignment, then we want to assign something new into it. If successful, OK, if not, that object should keep its original content logically. Not? The same for the other catch (line 31).
Last edited on
1. Major reason of temporary is that elem will be delete[] and assigned to nullptr if new throws, otherwise you have pointer to deleted memory which is then known as dangilng pointer which would make the program crash if it's used, because dangling pointer is not comparable to nullptr, there is no way to check if some pointer is dangling, that's why it's important to be assigned to either something or nullptr

also deleting nullptr won't have any effect
1
2
int* x = nullptr;
delete x; // no-op (nothing happens here)  


while

1
2
int* x; // dangling pointer
delete x; // ASDF!! 


2. yes
3.
why destroying it!?
because left hand side was supposed to be assigned a new value, if you retain old value then the program may proceed in unwanted direction, that is the result of some operation will no longer be accurate.

it's preferred to to throw or rethrow in such cases, and handle exception elsewhere but not before resources are released, because the point of exception handling is to fix the problem at runtime while making sure you don't leak memory and then continue execution, otherwise if it can't be fixed at runtime you should call std::abort()

For example in this specific case you could solve the problem inside catch block by allocating memory from your own memory pool preallocated at program startup which you reserved for allocation errors.
Last edited on
Thank you.
Isn't routine to handle an exception the following way in catches, please?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// create new storage, could throw
                  p = new double[a.sz];

...

  catch (std::bad_alloc& ex)
            {
                  delete[] elem; // delete old elements
                  elem = nullptr;
                  sz = 0;
                  cerr << ex.what() << "\n";
                  abort();
            }
...
Last edited on
std::abort() will terminate program, once the program is terminated system will reclaim all memory assigned to process.

Knowing that, if your choice is to abort then there is no reason to do cleanup or anything else, except showing a message what happened:

1
2
3
4
5
6
catch (std::bad_alloc& ex)
{
         // Just show the message, system will do cleanup
         cerr << ex.what() << "\n";
         abort();
}


Otherwise if you handle this error inside catch block you do cleanup, fix the problem and the execution continues as if nothing happened:

1
2
3
4
5
6
7
8
9
10
catch (std::bad_alloc& ex)
{
         delete[] elem;

         // Allocate memory from reserve pool
         elem = new (my_pool.allocate(a.sz)) double[a.sz]
         // ...
         sz = a.sz
         return *this;
}


Otherwise if you rethrow, (up in call stack) then you should release:

1
2
3
4
5
6
7
8
9
10
catch (std::bad_alloc& ex)
{
         delete[] elem;
         elem = nullptr;
         sz = 0;

         // Let some other code handle this error, higher in call stack
         cerr << ex.what() << "\n";
         throw; // rethrow (throw outside this function)
}


Note that if you rethrow but outside block doesn't handle the error the program will crash, or if debugger is present it will break.
Last edited on
Thanks for your info.

I've got a couple of new questions!

1- What type of catches above is commoner, please? I mean, to show a message and terminate the program because new couldn't grab some memory, or to fix it using memory from reserve pool which is completely new for me? (haven't heard of it before!) :(

2- And, allocating memory from heap may occur in programs frequently. Do we need to provide those collection of try-catches for each and every allocation? Yes, I guess. But in what situations allocation may fail, when we need a huge amount of memory (like a vector of thousands of doubles, for instance) or when the target device on which the program runs lacks much memory, or both?

My purpose is to use an approach not to set all those try-catches (exception handling) for each and every heap memory allocation, since not all new fail in reality. And Stroustrup didn't use it either. Page 99 Tour of C++


By the way, as the last attempt I tried to drop the temporary pointer this way. :) (I hope it hasn't a bug)

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
Vector& Vector::operator=(const Vector& a) // copy assignment
{
      if (this != &a)
      {
            try
            {
                  // make sure target pointer valid
                  if (a.elem == nullptr)
                        throw std::invalid_argument;

                  delete[] elem; // delete old elements (free the allocated storage)

                  // create new storage, could throw
                  elem = new double[a.sz];

                  // copy elements from a, now it's safe
                  for (int i=0; i!=a.sz; ++i)
                       elem[i] = a.elem[i];
                sz = a.sz;
            }

            catch (std::bad_alloc& ex)
            {
                  cerr << ex.what() << "\n";
                  abort();
            }
            catch (std::invalid_argument& ex)
            {
                cerr << ex.what() << "\n";
                abort();
            }
      }

      // if "a" is "this" object, there is nothing to do
      return *this;
}

Last edited on
What type of catches above is commoner, please?

That depends on a lot of things, ie. design behind your project, exception handling policy etc.
some people skip these things btw..

Do we need to provide those collection of try-catches for each and every allocation?

You can also write just one catch in entry program, it will catch everything, but very likely not useful and means stopping program anyway:

1
2
3
4
5
6
7
8
9
10
11
12
int main() try
{
     // entry point here
}
catch(std::bad_aloc& ex)
{
       // handle memory errors here
}
catch(...)
{
     // handle all other errors here since I hate exception handling
}


By the way, as the last attempt I tried to drop the temporary pointer this way. :) (I hope it hasn't a bug)


Not bad, but since you just abort all you need to do is write msg and abort, nothing else ;)
You should now declare your operator as noexcept to let compiler optimize it.
Last edited on
Thanks. How about this question, please? :)
But in what situations allocation may fail at a higher likelihood, when we need a huge amount of memory (like a vector of thousands of doubles, for instance) or when the target device on which the program runs lacks much memory, or both?

but very likely not useful and means stopping program anyway
Yes, but still terminating the program and providing the user with a message to know what happened is better than a crash without a message to show the reason, not?

I also need to read about memory from reserve pool.
Last edited on
But in what situations allocation may fail at a higher likelihood

See this link:
https://docs.microsoft.com/en-us/archive/msdn-magazine/2003/september/don-t-let-memory-allocation-failures-crash-your-stl-applications

terminating the program and providing the user with a message to know what happened is better than a crash without a message to show the reason, not?


Well yes, but don't abuse exception handling in such way where you avoid writing safe code just because "user will get the message" anyway.

exception handling purpose is as I already told you to catch the exception, fix the problem and continue execution as if nothing happened, not to stop program.

If anyone tell you that writing try catch blocks is waste of source file space or that checking for function return value is meaningless, just please don't listen to him.

Safe code is far more important than performance, you don't sacrifice safe code for performance. lazy-ness to write a few lines more is out of question.

I also need to read about memory from reserve pool.

That's advanced topic with a lot of advantages to improve program execution speeds, it's often used in game development.

You can google out some examples, most important is to write a good algorithm, memory allocation speeds usually go above 400% of normal allocation.
But how useful it will be depends on your project, if you don't doo a lot of allocations in short time it's probably not worth it.
exception handling purpose is as I already told you to catch the exception, fix the problem and continue execution as if nothing happened, not to stop program.
That's right, but easy in saying. Suppose we have various types and containers in a program allocating memory from heap. To cope with the new failure in each we need to supply a catch for each to grab memory from reserve pool based on the type and amount of memory for specific that one to fix it properly. This way our code will be uninterestingly quite messy I imagine, so we need to be selective about what heap allocation we should care more about and supply catch for it. I saw the link, it's unfortunately too vast and advanced. I couldn't make benefits from that. Of course I can create a new thread on that.

You should now declare your operator as noexcept to let compiler optimize it.
But this way the function never throws an exception to catch!

But how useful it will be depends on your project, if you don't doo a lot of allocations in short time it's probably not worth it.
But we have to use it to fix the exception, not? Is there any better way? Also I searched some keywords for that but didn't find a specific reference to talk about that! :(

Herb Sutter did write Guru of the Week (GotW) items about exception safety, published books Exceptional C++ and More Exceptional C++ based on those items, and has revised the material after C++11.
https://herbsutter.com/2013/05/04/guru-of-the-week-and-the-exceptional-c-series/

That is enlightening material.
In Stroustrup's version, if anything throws an exception, the vector is left in it's original state. The assignment either works completely or does nothing, and that's a good thing. The only bad thing is that it will leak memory if the new succeeds but, say one of the assignments throws.

On the other hand, your version will leave *this in an inconsistent state of anything throws an exception because you delete element at the very beginning an the vector isn't consistent again until the very last assignment.

There's another nice effect of Stroustrup's version that hasn't been mentioned: if you assign a vector to itself, it resizes the buffer to match the exact size of the vector. Okay, the vector as written doesn't let you erase items, but if it did, this could be important:
1
2
3
4
Vector v(1000);
// Populate v
// Now erase 500 elements in v.
v = v;  // Stroustrup's methoid resizes the buffer to 500. 


It return nothing and the control goes out of the function to the assignment. So nothing happens which what we expect when we assign an object to itself. Not true?

Here's the problem.
1
2
3
Vector a(10), b(10), c(10);
a = b = c;  // okay
a = b = b; // Oops. (b = b) doesn't return anything, so you try to assign garbage to a. 


please tell me what the problem is with my prior code and why still we need that pointer p in it:
Your code works most of the time, but Stroustrup's works in some error conditions where yours fails.

On line 24: if we can't allocate memory by new and can't copy the right hand object (a) to the left one (&this), why destroying it!?
It's a judgement call, but I'd agree with you. More importantly (to me) is that malibor's version doesn't give any indication to the caller that the problem occurred, so the caller will happily go on, assuming that the assignment succeeded when in reality, it cleared the vector. That's why I think it's better to leave the vector in it's original state and let the caller deal with the exception, which is exactly what Stroustrup did.

Isn't routine to handle an exception the following way in catches, please?
Maybe. Maybe not. The point is who are you to tell the application writer what should happen to their program? Maybe they want to abort. Maybe they don't. Shouldn't that be their decision?

Also, and probably far more important, your catch block has this:
cerr << ex.what() << "\n";
What makes you think that this code won't try to allocate memory itself?

And herein lies the great difficulty of dealing with out-of-memory conditions. Unless you're 100% certain that your recovery code won't attempt to allocate even 1 byte of memory, you run the risk of getting another allocation exception. For this reason, the only safe way I've seen to deal with out of memory is to allocate a reserve when the program starts. If you run out of memory, free the reserve immediately and then hobble your way through whatever cleanup code is needed.

Where I work, we made a deliberate decision to not check for out-of-memory conditions. It would make the code twice as big and chances are excellent that we wouldn't handle the problem right anyway. Instead, we check for low memory conditions at strategic points in the code and exit cleanly if memory is low. This works well. Years go by without ever getting an actual out-of-memory condition. I realize that this sounds like saying "Stroustrup's method doesn't matter," but I'm really just saying that this is how my company decided to handle the situation. Stroustrup's method is more robust, and that's always good.

Safe code is far more important than performance, you don't sacrifice safe code for performance. lazy-ness to write a few lines more is out of question.
It's a real shame that they don't seem to teach error checking in schools. In the real world, you always need to check for errors. Some quote from me and coworkers on the subject:
"Jmon (a program) processes errors. Doing useful work just a side gig."
"The biggest perofrmance boost is going from not working, to working."
"Code can run arbitrarily quickly if correctness isn't a factor."
if anything throws an exception, the vector is left in it's original state. The assignment either works completely or does nothing

ah, that makes sense.
cerr << ex.what() << "\n";
What makes you think that this code won't try to allocate memory itself?

@dhayden
doesn't the exception class store a const char* so there shouldn't be a memory allocation.
doesn't the exception class a const char*

There is also ostream::operator<<. Are you sure it doesn't allocate?
But we have to use it to fix the exception, not? Is there any better way? Also I searched some keywords for that but didn't find a specific reference to talk about that! :(


Good news for you, you can create memory pool quite easily by using standard C++

First include header:

#include <memory_resource>

Next create memory resource which will feed your pool at program startup, and also if you exhaust the pool:

std::pmr::memory_resource* upstream = std::pmr::new_delete_resource();

Next create memory pool:

std::pmr::unsynchronized_pool_resource pool(upstream);

Now the pool allocated default amount of memory, and you request some memory from it any time you need, for example:

void* memory = pool.allocate(sizeof(int));

This will return a pointer to memory large enough for an int note that this does not allocate memory at this point, memory has already been allocated, you only request preallocated memory here, therefore much faster and more safe.

Now to use this memory ex. for your pointer after failed new, you use placement new like this:
int* test = new (memory) int; // use preallocated memory

Here is a fully working memory poll code with comments for you to experiment,
note that you create a pool somewhere globally so that you can request some memory from pool, anywhere or anytime you need it.

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
#include <iostream>
#include <memory_resource>
namespace pmr = std::pmr;

int main()
{
	// create new memory resource
	pmr::memory_resource* upstream = pmr::new_delete_resource();

	if (upstream)
	{
		// set pool options with 10 units of memory where
		// largest unit is 1000 bytes of memory
		pmr::pool_options options;
		options.max_blocks_per_chunk = 100;
		options.largest_required_pool_block = 1000;

		// create new memory pool
		pmr::unsynchronized_pool_resource pool(options, upstream);

		// test pointer
		int* test = nullptr;

		// request memory for our int pointer
		// NOTE: memory is allocated at program startup,
		// we just request a bit of it for our pointer!
		void* memory = pool.allocate(sizeof(int));

		// construct int from preallocated memory
		test = new (memory) int;

		if (test)
		{
			// write data into memory
			*test = 100;
			std::cout << *test << std::endl;

			// tell pool to mark this memory as usable again
			// NOTE: it does not delete it
			pool.deallocate(memory, sizeof(int));
                        test = nullptr;
		}

		// tell memory pool to release memory (deletes all memory)
		pool.release();
	}
}


Note that you don't delete any memory here with delete
Last edited on
There is also ostream::operator<<. Are you sure it doesn't allocate?

How can I be sure? I am not a language lawyer.
Since cerr is unbuffered I don't see a need for memory allocation.
@dhayden
Instead, we check for low memory conditions at strategic points in the code and exit cleanly if memory is low. This works well.
I would like to know more about this approach, and if possible use it for my vector class, please.

@malibor
Thanks for your help but I suppose that's too much huge and complicated!!Do you think it's commonly used?
I would like to know more about this approach, and if possible use it for my vector class, please.
You're missing the point. We do the memory checking at the application level so that things like vector classes can just assume that new will work.
Pages: 123