Rzucanie wyjątkami - czy robię to dobrze?

0

Witam,
mam za zadanie zrobić prosty kontener i obsłużyć wszystkie wyjątki typu wielkość tablicy równa 0, itd. Wszystko działa i to całkiem fajnie, ale pewnie w tym moim kodzie byłoby coś zwanego "zła praktyka", albo raczej "brak dobrej praktyki". Prosiłbym o "zrecenzowanie" moich prób, co należałoby poprawić / można by było zrobić lepiej.

Exceptions.hh

#include <iostream>

const std::size_t TAB_SIZE_MAX = 10000;
const std::size_t TAB_SIZE_MIN = 3;

class ZeroStackSizeException{};

class BaseArraySizeException
{
protected:
	std::size_t size_;
public:
	BaseArraySizeException(std::size_t size) : size_(size) {}
	~BaseArraySizeException() {}

	virtual void printError() const = 0;
};

class TooSmallArrayException : public BaseArraySizeException
{
public:
	TooSmallArrayException(std::size_t size) : BaseArraySizeException(size) {}
	void printError() const
	{
		std::cerr << "Rozmiar tablicy: " << size_ << " jest mniejszy niz " 
		<< TAB_SIZE_MIN << "!" << std::endl;
	}
};

class ZeroArraySizeException : public TooSmallArrayException
{
public:
	ZeroArraySizeException() : TooSmallArrayException(0) {}
	void printError() const
	{
		std::cerr << "Rozmiar tablicy nie moze byc rowny 0!" << std::endl;
	}
};

class TooBigArrayException : public BaseArraySizeException
{
public:
	TooBigArrayException(std::size_t size) : BaseArraySizeException(size) {}
	void printError() const
	{
		std::cerr << "Rozmiar tablicy: " << size_ << " jest wiekszy niz " 
		<< TAB_SIZE_MAX << "!" << std::endl;
	}
};

Buffer.hh

 #ifndef _BUFFER_H
#define _BUFFER_H

template <typename T>
class Buffer
{
protected:
	T* arr_;
	std::size_t capacity_;
	std::size_t size_;
public:
	Buffer(std::size_t);
	Buffer(const Buffer &);
	virtual ~Buffer();
	Buffer<T>& operator = (const Buffer&);
};

#endif // _BUFFER_H

Buffer.cc

 #include <iostream>
#include <new>
#include <stdexcept>

#include "Buffer.hh"
#include "Exceptions.hh"

template <typename T>
Buffer<T>::Buffer(std::size_t capacity) : capacity_(capacity), size_(0)
{
	if(capacity_ == 0)
		throw ZeroArraySizeException();
	if(capacity_ < TAB_SIZE_MIN)
		throw TooSmallArrayException(capacity_);
	if(capacity_ > TAB_SIZE_MAX) 				
		throw TooBigArrayException(capacity_);
	try
	{
		arr_ = new T[capacity_];
	}
	catch(const std::bad_alloc &e)
	{
		std::cerr << "Nie udalo sie zaalokowac pamieci: " << e.what() << std::endl;
	}
}

template <typename T>
Buffer<T>::Buffer(const Buffer &buffer) : capacity_(buffer.capacity_), 
										  size_(buffer.size_)
{
	try
	{
		arr_ = new T[capacity_];
	}
	catch(const std::bad_alloc &e)
	{
		std::cerr << "Nie udalo sie zaalokowac pamieci: " << e.what() << std::endl;
	}

	for(std::size_t i = 0; i < size_; ++i)
		arr_[i] = buffer.arr_[i];
}

template <typename T>
Buffer<T>::~Buffer()
{
	delete [] arr_;
}

template <typename T>
Buffer<T> & Buffer<T>::operator = (const Buffer &buffer)
{
	if(this != &buffer)
	{
		delete [] arr_;
		capacity_ = buffer.capacity_;
		size_ = buffer.size_;
		try
		{
			arr_ = new T[capacity_];
		}
		catch(const std::bad_alloc &e)
		{
			std::cerr << "Nie udalo sie zaalokowac pamieci: " << e.what() << std::endl;
		}
		for(std::size_t i = 0; i < size_; ++i)
			arr_[i] = buffer.arr_[i];
	}
	return *this;
}

Stack.hh

 #ifndef _STACK_HH
#define _STACK_HH

#include <iostream>
#include "Buffer.hh"

template <typename T>
class Stack : public Buffer<T>
{
public:
	Stack(const std::size_t capacity) : Buffer<T>(capacity) {}
	Stack(const Stack &buffer) : Buffer<T>(buffer) {};
	Stack<T>& operator = (const Stack&) = default; // to implementuje kompilator
	~Stack() = default;	// i to tez (tak dziala slowo default)

	void push(const T&);
	void pop();
	T& top();
};

#endif // _STACK_HH

Stack.cc

#include "Stack.hh"
#include "Exceptions.hh"

template <typename T>
void Stack<T>::push(const T& el)
{
	if(this->size_ + 1 > this->capacity_)
		throw std::out_of_range("Przepelnienie zakresu stosu!");
	this->arr_[this->size_++] = el;
}

template <typename T>
void Stack<T>::pop()
{
	if(this->size_ == 0)
		throw ZeroStackSizeException();
	--(this->size_);
}

template <typename T>
T& Stack<T>::top()
{
	if(this->size_ == 0)
		throw ZeroStackSizeException();
	return this->arr_[this->size_ - 1];
}

Main.cc

#include <iostream>
#include <new>
#include <stdexcept>

#include "Exceptions.hh"
#include "Buffer.cc"
#include "Stack.cc"

void f1();
void f2();

void f1()		
{
	//Stack<int> abcd(0);
	//Stack<int> abc(2);
	//Stack<int> ab(1e10);
  	Stack<int> a(4);
    //a.top();
    //a.pop();
    a.push(1);
    a.push(2);
    a.push(3);
    a.push(4);
    std::cout << "Ostatni element: " << a.top() << std::endl;
    //a.push(5);
    a.pop();
    std::cout << "Ostatni element: " << a.top() << std::endl;
}


void f2()
{
	try
	{
		f1();
	}
	catch(BaseArraySizeException &e)
	{
		e.printError();			// wykorzystanie polimorfizmu
	}
	catch(const std::out_of_range &e)
	{
		std::cerr << e.what() << std::endl;
	}
	catch(ZeroStackSizeException)
	{
		std::cerr << "Jesli chcesz usunac lub zwrocic ostatni element, " <<
		 			 "to stos nie moze byc pusty!" << std::endl;
	}
	catch(const std::exception &e)
	{
		std::cerr << "Wystapil nieznany blad z biblioteki standardowej: " << e.what() << std::endl;
	}
	catch(...)
	{
		std::cerr << "Wystapil nieznany blad!" << std::endl;
	}
}

int main(void)
{
	f2();
	return 0;
}

jeśli ktoś chciałby sobie skompilować kod, to w załączniku jest kod + makefile

2

Dobrą formą jest używanie wyłącznie klas wyjątków dziedziczących po std::exception. Gdybyś to robił uniknąłbyś od razu kolejnego problemu - usilnego wypisywania na wyjście standardowe, nawet gdy nie ma to sensu. Twoje wyjątki będą mało pomocne jeśli zaczniesz pisać aplikację okienkową.

0

Taką strukturę programu (rozpis klasy Buffer, Stack oraz funkcje f1(), f2()) miałem z góry narzucone przez autora zadania

Dobrą formą jest używanie wyłącznie klas wyjątków dziedziczących po std::exception.

To jakby wtedy wyglądało to dla np.

try {int tab[0]} 
catch(??)
0

Aha, w takim razie krytyka pozostaje bez zmian, tylko adresat jest inny ;​)

@Edit no cóż, tablica nie może dziedziczyć po niczym (zakładam, że chciałeś ją rzucić). Nie ma nic złego w utworzeniu własnej klasy wyjątku - ale już można się czepiać jeśli rzucasz coś, czego nie można złapać łapiąc std::exception const&

0

Generalnie chodzi mi o coś takiego jak w przykładzie tu: http://en.cppreference.com/w/cpp/memory/new/bad_array_new_length
Tyle, że z rozmiarem tablicy równym 0
I tak, sorry, że źle się wyraziłem

2

Ach, no to jest w pełni zgodne z tym co mówię: bad_array_new_length dziedziczy z bad_alloc, które to dziedziczy z exception :​)

1 użytkowników online, w tym zalogowanych: 0, gości: 1