You are on page 1of 8

Software Constraints

(Final Report)

Muhammad Ayaz Malik


8403140836
Group-8
Software Engineering and Technology, Chalmers
mayaz@student.chalmers.se

TA: Pelle Evensenn


Problem Solved: HW 1: Problems 1, 2, 3, 4; HW 2: Problems 1, 2, 3, 4; HW 3: Problems 1, 2, 3, 4; HW 4:
Problems 1, 2, 3, 4; HW 5: Problems 1, 2, 3, 4; HW 6: Problems 2, 3, 4;
Declaration: By submitting this report I confirm that the entire report is my own work and that it has been
written exclusively for this course. Furthermore, I confirm that I solved all exercises for which I was
awarded points, and solved them in a proper and presentable way. Ayaz

Abstract 2.1. What is exception safety?

When we talk about reusability, robustness, and We can say that a piece of code (component) is
efficiency in software, we always need to put exception safe if it leads itself to a consistent state after
constraints on our software practices (or coding style). throwing an exception. Consistent state means a well
These constraints can be implemented properly if we defined predictable state, for example a state with no
know good coding practices, language features and memory leakage etc. For a class instance (object) we
limitations. Thus “software constraints” governs can say that object is in consistent state if it is not
important concepts which we need to know to be a violating class invariants. Ignoring exception safety
good programmer and to write efficient and reliable could lead to premature termination (resource leak . .
software. .), inconsistent state.
Thus we have two concepts here:
1. Introduction Exception Safe: A component is exception safe
if all of its invariants are valid even in the presence of
This report is written as “final exam” for the exception.
course “Software Constraints” in Chalmers University Exception Neutral: if it propagates all of its
of technology. It is individual report. The purpose of exception, thrown by components type parameter to
this report is to write about the learning and my the caller.
experience about this particular course. I am free to
choose any topic from the course outline. The topic 2.2. Stack Code Analysis
which I choose is Exception Safety.
In this section we will analyse conventional stack
2. Exception Safety code to understand exception safety better and to see
why do we need it?
predict in which order the initializer list will be called.
2.2.1. Constructor Analysis: In Figure-1 you can see So you will not know whether v or v_spare is
an implementation of stack constructor. allocated first. Second problem is that if one pointer is
allocated successfully and an exception is raised
1 template<typename T> during the allocation of memory for second pointer,
2 class Stack than memory for the first pointer will not be released
3{ automatically. This could lead to memory leakage or
4 public: some unpredictable behaviour. We can fix this issue
5 Stack(): v(new T[10]), vsize(10), vused(0) {} by moving all initializations into the constructor body
6 // other methods omitted and then we can catch the exception and take some
7 }; appropriate action.
Design Guidelines:
Figure-1: Exception Safe Constructor 1. If function can not handle exception, it
should propagate it to the caller.
The stack constructor shows that three data 2. Always check for consistency for correct
members are initialized into initializer list . Now release of resources.
we need to see what can throw exception. We are
initializing v with an array of 10 elements of type T. 2.2.2. Destructor Analysis: Figure-3 illustrates about
“new” will allocate memory and also create objects of stack destructor. What if destructor will throw
T by calling T’s constructor. So exception can be exception?
thrown at two points.
1. If operator new [] fails 1 template<typename T>
2. If T::T() fails 2 class Stack
If new [] fails we have guaranty from the 3{
language that all allocated memory till exception will 4 public:
be deleted automatically. Same case will happen if 5 // ...
T::T() fails. In other words, in both cases at the end we 6 ~Stack() { delete [] v; }
will have no memory allocated and v will still point to 7 private:
null. So as we have no memory leakage, we can say 8 T* v; // pointer to memory for <enough> T objects
that object of stack is in consistent state, which 9 // ...
concludes that constructor of stack in figure-1 is 10 };
exception safe.
Figure-3: Exception Safe Destructor
1 template<typename T>
2 Stack<T>::Stack(): v(new T[10]), It is good practice to make destructor not throw any
v_spare(new[100]), vsize(10), exception, because if it throw than it is very difficult to
vused(0) {}; have predictable behaviour of the code. Above
destructor is exception safe because delete will not
Figure-2: Exception unsafe Constructor throw any exception (language guaranty).
Design Guidelines:
Let analysis constructor of another stack 1. Never allow a destructor to throw an
implementation as shown in figure-2. Now we have exception.
two pointers being initialized with new operator. The
first problem with above code is that you can not 2.2.3. Copy Constructor Analysis (Helper
Function): Code is Figure-4 is actually a which is made exception safe by using helper function
demonstration of a helper function which could be NewCopy. If NewCopy will throw exception, no
used in a copy constructor to make it exception safe. memory will be allocated for v.
Design Guidelines:
1 template<typename T> 1. Separate code which can cause exception.
2 T* Stack<T>::NewCopy(const T* src, size_t 2. When component has completed its task
source_size, than update program state.
3 size_t dest_size)
4{ 2.2.4. Member Function analysis (Stack::pop): If
5 assert(dest_size >= source_size); // sanity check exception occurs on copy constructor of T (line 8,
6 T* dest = new T[dest_size]; Figure-6), the value will be still popped from the
7 try { stack.
8 std::copy(src,src + source_size, dest); // STL
algorithm 1 template<typename T>
9} 2 T Stack<T>::pop() {
10 catch(...) // catch all 3 if (vused == 0) {...}
11 { 4 else
12 delete[] dest; // assume no-throw 5{
13 throw; 6 T result = v[vused - 1];
14 } 7 --vused;
15 return dest; 8 return result;
16 } 9 }}
10 // client code, assume s is of type
Figure-4: Helper function for Copy Constructor std::stack<std::string>
11 std::string s1(s.pop());
The NewCopy is exception safe and exception neutral 12 std::string s2;
because if std::copy will throw exception than we will 13 s2 = s.pop();
delete allocated memory for dest pointer and will Figure-6: Exception safety flaw Stack::pop
propagate that exception to caller.
Now no client of T statck::pop() can write code which
1 template<typename T> is exception safe. So we can solve this problem by
2 class Stack returning result by reference or by separating code of
3{ return from pop. We can do that by splitting
4 public: conventional pop into two functions 1. Pop with no
5 Stack(const Stack& other): v(NewCopy(other.v, return. 2. A new function Top, which will return top
other.vsize, element of stack (This approach is also followed in
6 other.vsize)), stack interface in STL).
7 vsize(other.vsize), Design Guidelines:
8 vused(other.vused) {} 1. A function which is changing the state of T
9 ... object should never return T by value.
10 };
Figure-5: Exception Safe Copy Constructor 2.3. Hidden Execution Paths

Figure-5 is about exception safe copy constructor Hidden execution paths are of much importance
in exception safety. Often we think about a code that it I found 3 problems related to exception safety in
is exception safe but actually it is not. It is because we homework. Two problems are in Home work 3 and
don’t think about the hidden execution paths (in case one is in home work 4.
of exceptions or hidden object creation etc). So it is
very necessary to keep in mind all execution paths of a 2.5.1. Homework-3 Problem-3:
particular code. Problem: The problem was about identifying as
much execution paths (hidden or visible) as we can
2.3.1. Example: In code example of Figure-5 we from the code in figure-8.
have hidden call to X::~X() at line 6 and 7.
1 string Ladok_lookup(student s)
1 ErrorCode fun( int& result ) 2{
2{ 3 if (s.course() == ‘‘TDA612’’ || s.grade() > failed)
3 X x; 4{
4 ErrorCode err = x.g( result ); 5 std:: cout << s.first() << ‘‘ ‘‘ << s.last();
5 if ( err != kNoError ) 6 std:: cout << ‘‘ has passed’’ << std::endl;
6 return err; 7}
// ...More code here... 8 return s.first() + ‘‘ ‘‘ + s.last();
7 return kNoError; 9}
8} Figure-8: Homework-3 Problem-3
Figure-7: Hidden Execution Paths
Solution: For solution of the problem look
You can expect more hidden execution paths in case of appendix-A.
exceptions. For Example if copy constructor of Learning Outcome: Hidden execution paths are
ErrorCode at line 4 will throw exception than we will of very importance for making component exception
have a new execution path. safe. So for me this problem was very interesting to
learn something. I have learned form the problem that
2.4. Safety Levels how to find hidden execution paths and which kind of
code has high potential to throw and exception and
The generic component might provide one of the which have low potential. Every user defined code has
following safety levels or guarantees for its client. potential to throw an exception and for language
1. Basic Guarantee: Invariants of the component is defined constructs we need to look for language
preserved and no resources are leaked. Example: specification. We can find hidden calls of destructors
Figure-1 is an example of basic guarantee. near return or end of function. Constructor and copy
2. Strong Guarantee: Operation either completed constructors are also called when we return by value.
successfully or it throws exception and will leave So we also need to look and by keeping them in mind,
its state unchanged. Example: Operator new has we need to write exception safe code.
strong Guarantee.
3. No Throw Guarantee: Component will never 2.5.2. Homework-3 Problem-4:
throw an exception. It is highest level of safety. Problem: This problem was about design trade
Example: Operator delete has no throw off while thinking about safety or efficiency. The
guarantee. problem has 4 parts.
Solution: For solution of the problem look
2.5. Related Homework Problems appendix-A.
Learning Outcome: While implementing
exception safety it could happen we are loosing Some times we need to negotiate with it. But we
efficiency in terms of space and time. This problem should follow a strategy which is exception safe as
gave me idea what kind of design restrictions we may well as efficient.
face when we are going for exception safety. When I For making a component with strong guarantee
analyse vector<T>::insert code, I come to know that we can follow a general approach. For example: we
when it become difficult to implement strong have Operation Op which will perform some function
guarantee without loosing performance. I also learned on a container c. Before passing c to operation op, we
that why for some containers functions it is necessary can make copy of c and than we can pass that copy to
to have no throw guarantee (for example: op. and if op throws our main object c will remain
set<T>::erase) . unchanged. You can see this approach in figure-7.

2.5.3. Homework-4 Problem-1: template <class Container, class BasicOp>


Problem: This problem was analysis of some void MakeOperationStrong( Container& c, const
exception safe and unsafe code from an article BasicOp& op )
http://www.research.att.com/˜bs/3rd_safe0.html {
Solution: For solution of the problem look Container tmp(c); // Copy c
appendix-A. op(tmp); // Work on the copy
Learning outcome: I learned from this problem c.swap(tmp); // Cannot fail7
that which kind of ordinary mistakes could lead to }
serious issues some times. If we write code even Figure-9: Implement Strong Guarantee
without analysing its safety measures, it could lead to
wrong results and may be the failure of final product. I 4. Conclusion
learned how I can exception safe constructor,
destructor and other functions. When using template “Software Constraints” is very well designed
where I have fear of calling copy constructor and course specially discussions in homework session. I
hidden destructor (hidden calls) which could create really learned a lot from it. It opens a lot of ways to
problems. think while writing code. Now my code is safer and
efficient. Exception safety is really an issue to be
2.6. Design Trade off (Safety vs. efficiency) addressed in you code. It is very hard to find hidden
execution paths and the code which can throw
No throw is the ideal level for exception safety exceptions. But after reading and practicing this much
but it is not possible to achieve it in all cases. One can stuff, I am enough confident that I can write a piece of
think about strong guarantee. But what are the possible code which is exception safer.
trade off in terms of efficiency? if we try to implement
strong guarantee. Lets think about the References
vector<T>::insert code (Section 2.5.2). For insertion at
start or at the end may not be much difficult to reverse [1] http://www.boost.org/community/exception_safety.html
but what about insertion at the middle. In case of
exception you need to delete element from middle and [2] Course slides & Homework
you need to move all elements to left (For Strong
guarantee view section 2.4) so that you will have same [3] http://www.research.att.com/˜bs/3rd_safe0.html
container after exception. This means you need to
write more code and may be you need more memory to
implement strong guarantee. This is design trade off.
Appendix-A

Homework-3 Problem-3: Execution Paths

Non-Exceptional:
1. 3 (Both False), 8
2. 3 (Left One is true, right one will not execute), 5, 6, 8
3. 3 (Left False, Right True), 5, 6, 8

Exceptional:
1. 3 (Exception on s.course()), Exit
2. 3 (Exception on ==), Exit
3. 3 (Exception on s.grade()), Exit
4. 3 (success), 5 (Exception on s.first()), Exit
5. 3 (success), 5 (Exception on s.last()), Exit
6. Enough.................................

Homework-3 Problem-4: Safety vs. Efficiency

Part#1:
vector<T>::insert. Insertion into the middle of a vector requires copying elements after the
insertion point into later positions, to make room for the new element. If copying an element can fail, rolling
back the operation would require “undoing” the previous copies...which depends on copying again. If
copying back should fail (as it likely would), we have failed to meet our guarantee. So make a new copy
every time could be one solution but it is useless when we need to insert at the end.

The basic guarantee is a “natural” level of safety for this operation, which it can provide without violating its
performance guarantees.

Part#2:
Functions with strong guaranty:
http://www.stlport.org/doc/exception_safety.html

vector<T, A>::reserve(size_type n);


Specification:
1. It used to increase capacity of a vector.
2. If n is less than or equal to capacity(), this call has no effect. Otherwise, it is a
request for allocation of additional memory. If the request is successful, then
capacity() is greater than or equal to n; otherwise, capacity() is unchanged. In
either case, size() is unchanged.
Possible Algo:
1. Allocate memory at the end, increase capacity
2. Allocate new memory, copy previous data, release previous, assign new, change
capacity
vector<T, A>::push_back(const T&);
It also has strong guaranty without overheads. Add element at end.

Part#3:

template <class T> // 1


void SearchableStack<T>::push(const T& t) // 2
{ // 3
set<T>::iterator i = set_impl.insert(t); // 4
try // 5
{ // 6
list_impl.push_back(i); // 7
} // 8
catch(...) // 9
{ // 10
set_impl.erase(i); // 11
throw; // 12
} // 13
} // 14

To rollback actions (line 4) we need to erase our element, if erase will fail then we don’t have any chance to
have our object in consistent state (invariant), so we need no-throw guaranty from erase function that it
would be completed successfully and will not throw any exception.

Part#4:
vector<T, A>::push_back(const T&);
It could be dependent upon the erase or delete memory for rollback (probably a no throw guaranty
needed from them).

Homework-4 Problem-1: Safe – Unsafe code analysis

template<class T> class Safe {


T * p ; // p points to a T allocated using new
public :
Safe () :p (new T ) { }
˜Safe () { delete p ; }
Safe & operator =(const Safe & a ) { *p = *a .p ; return *this ; }
/ / ...
};

template <class T > class Unsafe { // sloppy and dangerous code


T * p ; // p points to a T
public :
Unsafe (T * pp ) :p (pp ) { }
˜Unsafe () { if (!p ->destructible()) throw E(); delete p ; }
Unsafe & operator =(const Unsafe & a )
{
p ->˜T (); // destroy old value (§10.4.11)
new (p ) T (a .p ); // construct copy of a.p in *p (§10.4.11)
return *this ;
}
/ / ...
};

void f (vector < Safe <Some_type > >&vg , vector <Unsafe <Some_type > >&vb )
{
vg.at (1 ) = Safe <Some_type >();
vb.at (1 ) = Unsafe <Some_type >(new Some_type );
/ / ...
}

Observations:
Safe:
1. Constructor of safe will succeed only if T successfully constructed (Constructor and new can
through exception)
2. If T’s assignment operator always leaved its operands in good state, then there will be no problem
with assignment operator of Safe. Exception in T’s assignment operator will result in exception in
safe’s assignment operator.
3. Safe is well behaved and produce reasonable results.
4. Meaningful invariant
Unsafe:
5. Construction of unsafe will not fail.
6. The assignment operator may fail by throwing an exception from T’s copy constructor. This would
leave a T in an undefined state because the old value of *p was destroyed
7. Destructor is throwing an exception, Standard library requires that destructor should return normally.
8. Not a well defined invariant.

You might also like