Có 2 loại Exception: Đồng bộ: Ngoại lệ xảy ra khi có sự cố xảy ra do lỗi trong dữ liệu đầu vào hoặc khi chương trình không được trang bị để xử lý loại dữ liệu hiện tại mà nó đang làm
Trang 1BỘ GIÁO DỤC & ĐÀO TẠO TRƯỜNG ĐẠI HỌC KHOA HỌC TỰ NHIÊN, ĐHQG-HCM
KHOA CÔNG NGHỆ THÔNG TIN
BTLT-Group-W09-10
Bộ môn: Phương pháp lập trình hướng đối tượng
Mã lớp: CQ2022/5
GVHD:Hồ Tuấn Thanh
Tên nhóm: See2plus
Thành viên:
Nguyễn Đăng Trí MSSV: 22120383
Nguyễn Văn Tý MSSV: 22120419
Phạm Tuấn Anh MSSV: 22120452
Thành phố Hồ Chí Minh, ngày 14 tháng 05 năm 2024
Trang 2GVHD: Hồ Tuấn Thanh BTLT-Group-W09-10
Mục lục
SECTION 1: Phân công nhiệm vụ và đánh giá mức độ hoàn thành 1
SECTION 2: Nội dung bài làm 2
1 (2đ) Tìm hiểu về exception handling trong C++ Cho ví dụ minh họa 2
2 (2đ) Tìm hiểu ít nhất 5 class trong std:exception Cho ví dụ minh họa 5
3 (2đ) Tìm hiểu nguyên lý L trong SOLID principles Cho ví dụ minh họa thực tế .11
4 (2đ) Tìm hiểu nguyên lý I trong SOLID principles Cho ví dụ minh họa thực tế .14
5 (2đ) Tìm hiểu nguyên lý D trong SOLID principles Cho ví dụ minh họa thực tế .16
SECTION 3: Danh mục tài liệu tham khảo 17
Trang 31
GVHD: Hồ Tuấn Thanh
SECTION 1: Phân công nhiệm vụ và đánh giá mức độ hoàn thành
1 (2đ) Tìm hiểu về exception handling
trong C++ Cho ví dụ minh họa
2 (2đ) Tìm hiểu ít nhất 5 class trong
std:exception Cho ví dụ minh họa
3 (2đ) Tìm hiểu nguyên lý L trong SOLID
principles Cho ví dụ minh họa thực
tế
4 (2đ) Tìm hiểu nguyên lý I trong SOLID
principles Cho ví dụ minh họa thực
tế
5 (2đ) Tìm hiểu nguyên lý D trong SOLID
principles Cho ví dụ minh họa thực
tế
Phân công nhiệm vụ, giám sát,
kiểm tra quá trình làm bài của các
thành viên trong nhóm Kiểm tra
bài làm, tổng hợp bài làm và viết
báo cáo
Trang 4BTLT-Group-W09-10 GVHD: Hồ Tuấn Thanh
SECTION 2: Nội dung bài làm
1 (2đ) Tìm hiểu về exception handling trong C++ Cho ví dụ minh họa.
Exception handling là gì?
Exception handling trong C++ là một cơ chế cho phép xử lý các tình huống ngoại lệ trong quá trình thực thi chương trình Khi một tình huống ngoại lệ xảy ra, chương trình sẽ nhảy đến các khối mã được xác định trước, được gọi là các khối "catch", để xử lý tình huống đó Điều này giúp chương trình có thể kiểm soát và xử lý các tình huống không mong muốn mà không cần phải dừng hoàn toàn hoặc crash
Có
2 loại Exception:
Đồng bộ: Ngoại lệ xảy ra khi có sự cố xảy ra do lỗi trong dữ liệu đầu vào hoặc khi chương trình không được trang bị để xử lý loại dữ liệu hiện tại mà nó đang làm việc, chẳng hạn như chia số cho không
Không đồng bộ: Các ngoại lệ nằm ngoài tầm kiểm soát của chương trình, chẳng hạn như lỗi đĩa, ngắt bàn phím, v.v
C++ try and catch.
C++ cung cấp một tính năng sẵn có cho Xử lý ngoại lệ Nó có thể được thực hiện bằng cách sử dụng các từ khóa chuyên ngành sau: “try”, “catch” và “throw” với mỗi từ khóa có một mục đích khác nhau
“Try” trong C++: đại diện cho một khối mã có thể ném một ngoại lệ được đặt bên trong khối thử Tiếp theo là một hoặc nhiều khối bắt Nếu một ngoại lệ xảy
ra, hãy thử chặn ném ngoại lệ đó
“Catch” trong C++: đại diện cho một khối mã được thực thi khi một ngoại lệ
cụ thể được ném ra khỏi khối thử Mã để xử lý ngoại lệ được viết bên trong khối bắt
“Throw” trong C++: Được sử dụng để ném một ngoại lệ Cũng được sử dụng
để liệt kê các trường hợp ngoại lệ mà một hàm ném ra, nhưng không xử lý chính nó
Trang 5#include <iostream>
#include <stdexcept>
using namespace std;
void process(int x)
{
throw runtime_error( Divide by zero exception");
else if (x < )0
throw out_of_range( Negative value exception");
else if (x > 100)
throw overflow_error( Value too large exception");
else
/
3
Ưu
điểm:
Tách mã xử lý lỗi khỏi mã thông thường: Trong các mã xử lý lỗi truyền thống, luôn có các điều kiện khác để xử lý lỗi Các điều kiện này và mã để xử lý lỗi được trộn lẫn với luồng bình thường Điều này làm cho mã ít đọc và có thể duy trì Với các khối try - catch, mã để xử lý lỗi trở nên tách biệt với luồng thông thường
Function / methods có thể xử lý bất kỳ ngoại lệ nào họ chọn: Một hàm có thể đưa ra nhiều ngoại lệ, nhưng có thể chọn xử lý một số ngoại lệ Các ngoại lệ khác được ném, nhưng không bắt được có thể được xử lý bởi người gọi Nếu người gọi chọn không bắt chúng, thì các ngoại lệ được xử lý bởi người gọi của người gọi Trong C++, một function có thể chỉ định các ngoại lệ mà nó ném bằng cách sử dụng từ khóa throw Người gọi hàm này phải xử lý ngoại lệ theo một cách nào đó (bằng cách chỉ định lại hoặc bắt nó)
Nhóm các loại lỗi: Trong C++, cả loại và đối tượng cơ bản đều có thể được ném thành ngoại lệ Chúng ta có thể tạo một hệ thống phân cấp của các đối tượng ngoại lệ, ngoại lệ nhóm trong không gian tên hoặc lớp, phân loại chúng theo các loại
Ví
dụ:
Trang 6BTLT-Group-W09-10 GVHD: Hồ Tuấn Thanh
Trong ví dụ này, hàm process kiểm tra nhiều điều kiện khác nhau để sinh ra các loại ngoại lệ khác nhau Trong hàm main(), chúng ta sử dụng một khối try-catch để bắt các loại ngoại lệ được sinh ra bởi hàm process Các loại ngoại lệ cụ thể như runtime_error out_of_range, , và overflow_error đều là các lớp con của lớp cơ
sở exception, vì vậy chúng ta có thể bắt chúng bằng cách sử dụng một khối catch đa dạng
Ngoài ra, chúng ta cũng có một khối catch cuối cùng mà không có thông tin về loại ngoại lệ cụ thể (catch ( )), điều này đảm bảo rằng bất kỳ ngoại lệ nào không được xác định trước cũng sẽ được bắt và xử lý
}
int main() {
try {
process(0);
//Các test case khác
//process(-1);
//process(101);
//process(10);
}
catch (const exception& ) {e
cerr << "Exception caught: " << e.what() << endl;
}
catch ( ) {
cerr << "Unknown exception caught" << endl;
}
return 0;
}
Trang 7BTLT-Group-W09-10 GVHD: Hồ Tuấn Thanh
5
Kết
quả khi hàm process truyền số 0:
2 (2đ) Tìm hiểu ít nhất 5 class trong std:exception Cho ví dụ minh họa.
2.1 class logic_error
Xác định một loại đối tượng được ném ra như một ngoại lệ Nó báo cáo các lỗi là hậu quả của lỗi logic trong chương trình, chẳng hạn như vi phạm các điều kiện tiên quyết logic hoặc bất biến lớp và có thể phòng ngừa được
Member Functions:
Constructor: khởi tạo 1 logic_error object với thông báo đã có
Operator=: Thay thế logic_error object
Trang 8#include < iostream >
#include < exception >
#include < vector >
using namespace std ;
int main ()
{
try
{
vector string < > students = { " Hoc " " , Tu " " , Tri " };
string myName = students at ( 5 ); // Hàm at() sẽ ném ra một logic_error nếu chỉ số ngoài giới hạn
cout << " My name: " << myName << endl ;
}
catch (const std :: logic_error & e )
{
cerr << " Caught a logic error: " << e what () << endl ;
}
}
Ví
dụ:
Output:
2.2 class bad_typeid
Một ngoại lệ của loại này được đưa ra khi toán tử typeid được áp dụng cho giá trị con trỏ null được tham chiếu của loại đa hình
Member Functions:
Constructor: khởi tạo 1 bad_typeid mới
operator=: thay thế bad_typeid
what: trả về chuỗi giải thích
Trang 9BTLT-Group-W09-10 GVHD: Hồ Tuấn Thanh
7
Ví
dụ:
#include < iostream >
#include < exception >
#include < vector >
using namespace std ;
class A
{
public:
virtual void print ()
{
cout << " A " << endl ;
}
};
class B : public A
{
public:
void print ()
{
cout << " B " << endl ;
}
};
int main ()
{
A * ptr = nullptr;
try
{
cout << typeid(* ptr ) name () << endl ;
}
catch (const std :: bad_typeid & e )
{
cerr << " Caught a logic error: " << e what () << endl ;
}
}
Trang 10#include < iostream >
#include
< exception > using
namespace std ; class
Base
{
public:
virtual ~Base () {}
};
class Derived : public Base
{
};
class AnotherClass : public Base
{
Output:
2.3 class bad_cast
Một ngoại lệ của loại này được ném ra khi dynamic_cast với một loại tham chiếu không thành công trong thời gian chạy và cả từ std::use_facet nếu khía cạnh được yêu cầu không tồn tại trong ngôn ngữ
Member Functions:
Constructor: Hàm khởi tạo một bad_cast object
operator=: Hàm thay thế một bad_cast object
what: trả về chuỗi giải thích
Ví
dụ:
Trang 11BTLT-Group-W09-10 GVHD: Hồ Tuấn Thanh
9
Output:
2.4 class bad_alloc
std::bad_alloc là loại đối tượng được các hàm phân bổ đưa ra dưới dạng ngoại lệ để báo cáo lỗi phân bổ bộ nhớ
Member Functions:
Constructor: Hàm khởi tạo một bad_alloc object
operator=: Hàm thay thế một bad_alloc object
what: trả về chuỗi giải thích
void testDynamicCast ( Base & base )
{
try
{
// lỗi khi cố gắng ép kiểu base sang kiểu derived
Derived & derived = dynamic_cast< Derived &>(base);
std :: cout << " Dynamic cast to Derived succeeded " << std :: endl ;
}
catch (const std :: bad_cast & e )
{
std :: cerr << " Caught a bad_cast exception: " << e what () << std :: endl ; }
}
int main ()
{
AnotherClass anotherObject ;
testDynamicCast ( anotherObject );
return 0 ;
}
Trang 12#include < iostream >
#include < exception >
int main ()
{
try
{
// Kích thước rất lớn, có thể gây ra lỗi cấp phát
size_t largeSize = static_cast< size_t >(- 1 );
int * largeArray = new int[ largeSize ];
largeArray [ 0 ] = 1 ;
std :: cout << largeArray [ 0 ];
delete[] largeArray ;
}
catch (const std :: bad_alloc & e )
{
std :: cerr << " Caught a bad_alloc exception: " << e what () << std :: endl ; }
return 0 ;
}
Ví
dụ:
Output:
2.5 class invalid_argument.
Xác định một loại đối tượng được ném ra như một ngoại lệ Nó báo lỗi phát sinh do giá trị đối số chưa được chấp nhận Ngoại lệ này được ném ra bởi std::bitset::bitset và các
họ hàm std::stoi và std::stof
Trang 13#include < iostream >
#include < exception >
#include < string >
using namespace std ;
int main ()
{
try
{
int num = stoi ( ABCD " );
cout << num << endl ;
}
catch ( invalid_argument const & ex )
{
cout << " Exception: " << ex what () << endl ;
}
}
11
Member Functions:
Constructor: Hàm khởi tạo một bad_alloc object
operator=: Hàm thay thế một bad_alloc object
what: trả về chuỗi giải thích
Ví
dụ:
Output:
3 (2đ) Tìm hiểu nguyên lý L trong SOLID principles Cho ví dụ minh họa thực tế.
Nguyên lý L trong SOLID principles là nguyên lý thay thế Liskov (Liskov Substitution Principle - LSP) Nội dung của nguyên lý như sau: “ Trong một chương trình, các object của class con có thể thay thế class cha mà không làm thay đổi tính đúng đắn của chương trình”
Trang 14class Payment
{
public:
virtual void pay(float money) = 0;
};
class CreditCardPayment : public Payment
{
public:
void pay(float money)
{
std::cout << "Processing credit card payment of " << money << std::endl;
}
};
LSP yêu cầu rằng các lớp con phải duy trì hành vi của lớp cha Điều này có nghĩa là nếu một lớp con kế thừa một lớp cha, nó phải có khả năng sử dụng ở bất kỳ đâu mà lớp cha được sử dụng mà không làm thay đổi tính đúng đắn của chương trình, cho phép dễ dàng thêm mới các lớp con mà không cần sửa đổi mã nguồn hiện có, giúp hệ thống linh hoạt hơn và dễ dàng mở rộng
Ví dụ minh họa: Trong cuộc sống, khi đi mua sắm, ăn uống, để thanh toán cho các loại chi phí thì có rất nhiều cách để thực hiện: thẻ ngân hàng, ví điện tử, Để triển khai vấn đề này thành 1 chương trình nên áp dụng nguyên lí L trong SOLID principles vào tối
ưu hóa chương trình
Trang 15BTLT-Group-W09-10 GVHD: Hồ Tuấn Thanh
13
class DigitalWalletPayment : public Payment
{
public:
void pay(float money)
{
std::cout << "Processing digital wallet payment of " << money << std::endl;
}
};
class PayPalPayment : public Payment
{
public:
void pay(float money)
{
std::cout << "Processing PayPal payment of " << money << std::endl;
}
};
class MobileAppPayment : public Payment
{
public:
void pay(float money)
{
std::cout << "Processing mobile app payment of " << money
}
};
Trang 16class Document {
public:
virtual void
virtual void
virtual void
virtual void
virtual void
};
createDocument( )
readDocument()
=
updateDocument
()
deleteDocument( )
= 0;
0;
= 0;
= 0;
convertDocument() = 0;
4 (2đ) Tìm hiểu nguyên lý I trong SOLID principles Cho ví dụ minh họa thực tế.
Nguyên lý I trong SOLID principles là nguyên lý phân tách giao diện ( Interface Segregation principle - ISP) Nội dung của nguyên lý I như sau: "Không nên bắt các khách hàng phải phụ thuộc vào những giao diện mà họ không sử dụng” Nói cách khác, thay vì tạo ra một giao diện lớn chứa nhiều phương thức mà không phải tất cả khách hàng đều cần đến, ta nên tạo ra nhiều giao diện nhỏ, chuyên biệt
Áp dụng nguyên lý ISP làm giảm sự phụ thuộc không cần thiết, các lớp chỉ phụ thuộc vào những giao diện mà chúng thực sự cần Khi các giao diện nhỏ gọn và chuyên biệt, việc thay đổi chúng ít ảnh hưởng đến các phần khác của hệ thống Ngoài ra cũng tăng khả năng tái sử dụng của các giao diện qua các lớp khác nhau mà không gây gánh nặng cho các lớp này
Ví
dụ minh họa: Khi tạo 1 interface Document với quá nhiều phương thức khác nhau: createDocument, readDocument, deleteDocument, và còn nhiều phương thức khác nữa, nhưng không phải loại tài liệu nào cũng cần sử dụng hết toàn bộ các phương thức đó, điều này vi phạm nguyên lí I trong SOLID principles
Thay vì tạo như 1 interface quá lớn như vậy thì nên áp dụng nguyên lí ISP vào trường hợp này để tối ưu hơn
Trang 17BTLT-Group-W09-10 GVHD: Hồ Tuấn Thanh
15
class CreateDocument
{
public:
virtual void createDocument() = 0;
};
class ReadDocument
{
public:
virtual void readDocument() = 0;
};
class UpdateDocument
{
public:
virtual void updateDocument() = 0;
};
class DeleteDocument
{
public:
virtual void deleteDocument() = 0;
};
class ConvertDocument
{
public:
virtual void convertDocument() = 0;
};
Trang 18BTLT-Group-W09-10 GVHD: Hồ Tuấn Thanh
5 (2đ) Tìm hiểu nguyên lý D trong SOLID principles Cho ví dụ minh họa thực tế.
Nguyên lý D trong SOLID là viết tắt của Dependency Inversion Principle Nội dung của nguyên lý này có 2 ý như sau:
“Các module cấp cao không nên phụ thuộc vào các module cấp thấp Cả hai nên phụ thuộc vào abstraction (interface).”
“Abstraction (interface) không nên phụ thuộc vào chi tiết Ngược lại, chi tiết (concrete implementation) nên phụ thuộc vào abstraction (interface).”
Nguyên lý này gợi ý cho chúng ta cách thiết kế và cài đặt chương trình tốt hơn về mặt nâng cấp, mở rộng, hay chỉnh sửa lại các chức năng Khi áp dụng nguyên tắc này, việc nâng cấp, mở rộng, chỉnh sửa các chức năng sẽ trở nên nhanh chóng và dễ dàng hơn
Vì khi đó, chúng ta sẽ chỉ cần chỉnh sửa trên các abstract class và các concrete class sẽ được tự động kế thừa Như vậy, chúng ta sẽ không cần phải chỉnh sửa trên tất cả các class liên quan đến chức năng mà chúng ta cần thay đổi
Một ví dụ thực tế và quen thuộc trong cuộc sống như sau: Chúng ta có rất nhiều thiết
bị điện tử khác nhau trong nhà như tivi, tủ lạnh, máy giặt, … Nhưng dù là thiết bị nào thì cũng sẽ có dây cắm là loại phích cắm 2 chấu Vậy thì mỗi khi chúng ta muốn sử dụng loại thiết bị nào đó thì chỉ cần lấy phích cắm nối vào ổ điện mà không cần quan tâm loại thiết
bị đó là gì
Trong ví dụ trên có thể xem phích cắm 2 chấu là một interface (abstraction), các thiết bị điện tử là một concrete class Khi cần thay đổi gì đó thì chỉ cần thay đổi trên interface (abstraction) là được Như ở Việt Nam thì hầu hết sử dụng phích 2 chấu, còn ở một số nước khác sẽ sử dụng phích 3 chấu Như vậy thì chỉ cần thay đổi interface (abstraction) 2 chấu thành 3 chấu là hoàn thành mà không cần thay đổi các thiết bị điện tử