Efficient Iteration << | Home | >> I want a vector of chars
2017-10-03
Vector v;
v.push_back(4);
v.push_back(5);
v[2]; // Out of bounds! (undefined behaviour) - may or may not crash
Can we make this safer?
Adding bounds checks to operator[]
may be needlessly expensive.
Could have a second, safer fetch operator:
class Vector {
...
public:
int &at (size_t i) {
if (i <= n) return theVector[i];
// else what?
}
};
v.at(2)
still wrong - what should happen?
- Return any
int
, looks like non-error - Returning a non-
int
, not type correct - Crash the program - can we do better? Don't return. Don't crash
Solution: raise an exception
class range_error {};
class vector {
...
public:
int &at(size_t i) {
if (i < n) return theVector[i];
else throw range_error{}; // Construct an object of range_error & "throw" it
}
};
- Client's options
- Do nothing
vector v; v.push_back(0); v.at(1) // The exception will crash the program
- Catch it
try { vector v; v.push_back(0); v.at(1); } catch (range_error &r) { // r is the thrown object, catch by reference saves a copy operation // Do something }
- Let your caller catch it
int f() { vector v; v.push_back(0); v.at(1); } int g() { try{ f(); } catch(range_error &r) { // Do something } }
- Exception will propogate through the callchain until a handler is found.
- Called unwinding the stack
- If no handler is found, program aborts (
std::terminate
gets called) - Control resumes after the catch block (problem code is not retried)
What happens if a constructor throws an exception?
- Object is considered partially constructed
- Destructor will not run on partially constructed object
Ex.
class C {...};
class D {
C a;
C b;
int *c;
public:
D() {
c = new int[100];
...
if (...) throw something {}; // (*)
}
~D() {
delete[] c;
}
};
D d;
- (*) the
D
object is not fully constucted, so~D()
will not run on d - But
a
andb
are fully constucted so their destructors will run - So if a constructor wants to throw, it must clean itself
class D {
...
public:
D() {
c = new int[100];
...
if (...) {
delete[] c;
throw something {};
}
}
}
}
What happens if a destructor throws?
Trouble
- By default, program aborts immediately
std::terminate
- If you really want a throwing destructor, tag it with
noexcept(false)
class myexception{};
class C {
...
public:
~C(_) noexcept(false) {
throw myexception{};
}
};
But watch out
void h() {
C c1;
}
void g() {
C c2;
h();
};
void f() {
try {
g();
} catch (myException &ex) {
...
}
}
- Destructor for
c1
throws at the end ofh
- Unwind through
g
- Destructor for
c2
throws as we leaveg
- No handler yet
- Now two unhandled exceptions
- Immediate termination guaranteed,
std::terminate
is called
Never let destructors throw, swallow the exception if necessary
Also note that you can throw any value, not just objects