Lập trình hướng đối tượng - Chương 6: Tạo và hủy

Như ta đã biết, trong C++ đã có sự cải thiện đáng kểhơn C đó là có thể gợp các kiểu dữ

liệu lại thành một cấu trúc, đó là lớp, một loại kiểu dữ liệu quan trọng điển hình trong C++.

Trong phần này chúng ta sẽ xem xét kĩ hơn về những vấn đề bên trong một lớp. Ở trong

phần trước đã đề cập đến việc xét quyền hạn cho lập trình người dùng (client programmer),

những gì người dùng được phép truy cập sử dụng và những gì bị hạn chế. ðiều này có nghĩa

là nội bộ cơ chế bên trong do người thiết kế lớp thiết lập.

Cùng với đó, việc kiểm soát truy cập đã cải thiện hơn và dễ dàng sử dụng. Khái niệm “Kiểu

dữ liệu mới” mà họ cung cấp có vài điểm tiến bộ hơn kiểu dữ liệu đã có trong C. Trình biên

dịch C++ có thể kiểm tra kiểu dữ liệu một cách chặt chẽ và đảm bảo với một cấp độ an toàn

hơn khi sử dụng kiểu dữ liệu.

Khi nói đến an toàn, có nhiều điều mà trình biên dịchcó thể làm cho chúng ta hơn là C đã

cung cấp. Khi lập trình với trình biên dịch C++ bạn sẽ cảm thấy mình dễ thấy lỗi hơn so với

C, thông thường là trước khi biên dịch chương trình, trình biên dịch sẽ báo lỗi (error) hay

cảnh báo (warning) và chĩ những chỗ lỗi cho bạn. Do vậy, bạn sẽ dễ dàng sữa lỗi và chương

trình của bạn sẽ có thể chạy được nhanh hơn (do tìm thấy lỗi và fix nhanh hơn)

Hai trong số vấn đề an toàn được đưa ra ở đây là tạo và hủy. Phần lớn lỗi trong C xảy ra là

do người sử dụng quên tạo hay hủy một biến

Trong C++, khái niệm về tạo và hủy lả rất quan trọng.Việc này cần thiết để loại bỏ những

lỗi nhỏ xảy ra khi người dùng quên đi các hoạt động này. Chương này sẽ giới thiệu các tính

năng trong C++ giúp đảm bảo cho việc tạo và hủy.

pdf17 trang | Chuyên mục: Lập Trình Hướng Đối Tượng | Chia sẻ: dkS00TYs | Lượt xem: 1400 | Lượt tải: 0download
Tóm tắt nội dung Lập trình hướng đối tượng - Chương 6: Tạo và hủy, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
h stringStash(sizeof(char) * bufsize); 
 ifstream in("Stash2Test.cpp"); 
 assure(in, " Stash2Test.cpp"); 
 string line; 
 while(getline(in, line)) 
 stringStash.add((char*)line.c_str()); 
 int k = 0; 
 char* cp; 
 while((cp = (char*)stringStash.fetch(k++))!=0) 
 cout << "stringStash.fetch(" << k << ") = " 
 << cp << endl; 
} ///:~ 
Bạn cũng chú ý rằng hàm cleanup() ñã bị loại bỏ, nhưng hàm hủy vẫn ñược gọi khi 
intStash và stringStash thoát khỏi vùng hoạt ñộng 
Stack với hàm tạo và hàm hủy 
Sau ñây là danh sách liên kết (bên trong Stack) với hàm tạo và hàm hủy cho thấy việc tạo 
và hủy gọn gàng với new và delete. Dưới ñây là file Header: 
//: C06:Stack3.h 
// With constructors/destructors 
#ifndef STACK3_H 
#define STACK3_H 
class Stack { 
 struct Link { 
 void* data; 
 Link* next; 
 Link(void* dat, Link* nxt); 
 ~Link(); 
 }* head; 
public: 
 Stack(); 
 ~Stack(); 
 void push(void* dat); 
 void* peek(); 
 void* pop(); 
}; 
#endif // STACK3_H ///:~ 
Không chỉ có hàm tạo và hàm hủy mà còn ñưa thêm vào struct Link: 
//: C06:Stack3.cpp {O} 
// Constructors/destructors 
#include "Stack3.h" 
#include "../require.h" 
using namespace std; 
Stack::Link::Link(void* dat, Link* nxt) { 
 data = dat; 
 next = nxt; 
} 
Stack::Link::~Link() { } 
Stack::Stack() { head = 0; } 
void Stack::push(void* dat) { 
 head = new Link(dat,head); 
} 
void* Stack::peek() { 
 require(head != 0, "Stack empty"); 
 return head->data; 
} 
void* Stack::pop() { 
 if(head == 0) return 0; 
 void* result = head->data; 
 Link* oldHead = head; 
 head = head->next; 
 delete oldHead; 
 return result; 
} 
Stack::~Stack() { 
 require(head == 0, "Stack not empty"); 
} ///:~ 
Hàm tạo Link::Link() ñơn giản tạo ra con trỏ data và next, còn trong Stack::Push(), 
dòng: 
 head = new Link(dat,head); 
không chỉ cấp phát vùng nhớ và còn tạo ra con trỏ cho vùng nhớ ñó 
Bạn có thể thấy kì kì tại sao hàm hủy của Link lại không làm gì, nói chính xác – tại sao 
không delete con trỏ data? Có hai vấn ñề ở ñây. Trong Stack, bạn có thể không ñúng khi 
delete con trỏ void nếu nó là ñối tượng. Còn nữa, nếu hàm hủy Link xóa con trỏ data, 
pop() sẽ trả về là một con trỏ bị xóa, ñiều này chắc chắn lỗi. Cái này cần nói ñến vấn ñề 
kiểm soát của: Link và Stack,chỉ lưu trữ con trỏ, nhưng không có trách nhiệm xóa ñi 
chúng. Vậy thì bạn cần phải biết trách nhiệm ñó phải thuộc về ai. Ví dụ như, nếu bạn không 
pop() và delete tất cả con trỏ trên Stack, thì hàm hủy sẽ không tự ñộng xóa các con trỏ 
trên Stack. Chú ý ñiều này sẽ dẫn ñến vấn ñề tràn bộ nhớ, vì vậy biết ñược ai có bổn phận 
hủy ñối tượng sẽ tạo ra sự khác biết giữa một chương trình hoàn chỉnh và, chương trình…gà 
– ðó là tại sao Stack::Stack() báo lỗi nếu ñối tượng Stack không trống khi hủy. 
Do việc cấp phát và hủy trong Link là ẩn bên trong Stack, phần ñó thực hiện ẩn – bạn 
không thể thấy khi test chương trình. Mặc dù bạn có trách nhiệm xóa con trỏ trở lại bằng 
pop() 
//: C06:Stack3Test.cpp 
//{L} Stack3 
//{T} Stack3Test.cpp 
// Constructors/destructors 
#include "Stack3.h" 
#include "../require.h" 
#include 
#include 
#include 
using namespace std; 
int main(int argc, char* argv[]) { 
 requireArgs(argc, 1); // File name is argument 
 ifstream in(argv[1]); 
 assure(in, argv[1]); 
 Stack textlines; 
 string line; 
 // Read file and store lines in the stack: 
 while(getline(in, line)) 
 textlines.push(new string(line)); 
 // Pop the lines from the stack and print them: 
 string* s; 
 while((s = (string*)textlines.pop()) != 0) { 
 cout << *s << endl; 
 delete s; 
 } 
} ///:~ 
Trong trường hợp này, tất cả những dòng trong textline ñược ñưa ra và xóa, nhưng nếu 
không, bạn sẽ nhận ñược từ hàm require() lời thông báo có nghĩa rằng…tràn bộ nhớ. 
Khởi tạo tập hợp (Aggrerate Initialization) 
Một tập hợp, là một ñống cái gì ñó gợp chung với nhau. ðịnh nghĩa này bao gồm nhiều kiểu 
gộp vào nhau (như struct và class). Mảng là tập họp của một kiểu. 
Khởi tạo tập hợp có thể dễ bị lỗi và dài dòng. Khởi tạo tập họp trong C++ sẽ an toàn hơn. 
Khi bạn khởi tạo một ñối tượng là một tập hợp, tất cả việc bạn cần phải làm chỉ là gán giá 
trị cho nó, còn việc tạo nó thì trình biên dịch ñã làm. Ví dụ tạo một mảng hoàn toàn ñơn 
giản 
 int a[5] = { 1, 2, 3, 4, 5 }; 
Nếu bạn thử thêm vào nhiều giá trị hơn mảng cho phép, trình biên dịch sẽ báo lỗi, còn nếu 
ít hơn thì không vấn ñề gì. Ví dụ: 
 int b[6] = {3}; 
Trong trường hợp này, trình biên dịch sẽ nhận giá trị ñầu tiên cho phần tử ñầu tiên của 
mảng, cụ thể trong trường hợp này là 3 cho phần từ b[0], các phần tử còn lại ñều nhận giá 
trị 0. Chú ý trường hợp này chỉ xảy ra nếu khi khai báo mảng có ít nhất một phần tử, còn 
nếu bạn khai báo một mảng không có giá trị nào như. Vì vậy, cách trên là một cách gọn ñể 
tạo một mảng toàn giá trị không mà không cần dùng vòng lặp nào, và không thể có một lỗi 
nào xảy ra. 
Một cách khác ñể mảng tự ñộng ñếm số phần tử, với cách này, trình biên dịch sẽ xác ñịnh 
ñộ lớn của mảng: 
 int c[] = { 1, 2, 3, 4 }; 
Bây giờ nếu bạn muốn thêm một thuộc tính cho mảng, bạn chỉ cần thêm một khai báo. Nếu 
bạn cần ñiều chỉnh code ở chỗ nào ñó, phần trăm bị lỗi của bạn sẽ giảm ñi. Nhưng làm sao 
ñể xác ñịnh số phần tử của mảng. Bạn hãy sử dụng toán tử sizeof c/sizeof*c, dù ñộ lớn của 
mảng có thay ñổi bạn vẫn có thể xác ñịnh ñúng số phần tử 
for(int i = 0; i < sizeof c / sizeof *c; i++) 
 c[i]++; 
Kiểu cấu trúc cũng là tập họp, trong C kiểu struct, tất cả ñiều ñược khai báo dưới dạng 
public: 
struct X { 
 int i; 
 float f; 
 char c; 
}; 
X x1 = { 1, 2.2, 'c' }; 
Nếu bạn có một mảng ñối tượng, bạn có thể khai báo gộp bằng cách sử dụng dấu ngoặc 
nhọn cho mỗi ñối tượng 
 X x2[3] = { {1, 1.1, 'a'}, {2, 2.2, 'b'} }; 
 ở ñây, ñối tượng thứ ba ñược tạo là 0 
Nếu kiểu là private, hoặc là public nhưng nằm trong hàm tạo, mọi thứ ñều khác hẳn. 
Trong ví dụ trên, người lập trình ñã gán trực tiếp cho tập họp, nhưng hàm tạo vẫn thực hiện 
bình thường. Ý là, hàm tạo luôn ñược gọi trong việc khởi tạo. Vì vậy nếu bạn có struct như 
vầy 
struct Y { 
 float f; 
 int i; 
 Y(int a); 
}; 
Bạn phải chỉ ra việc gọi hàm tạo. Cách tốt nhất là làm rõ ràng như sau: 
Y y1[] = { Y(1), Y(2), Y(3) }; 
Bạn tạo ba ñối tượng và ba hàm tạo ñược gọi. Dù cho bạn có hàm tạo hay không, dù struct 
với kiểu public hay class với kiểu private, khi ñối tượng ñược tạo ra ñều phải thông qua 
hàm tạo, khởi tạo tập họp cũng không ngoại lệ 
ðây là ví dụ về hàm tạo có tham số: 
//: C06:Multiarg.cpp 
// Multiple constructor arguments 
// with aggregate initialization 
#include 
using namespace std; 
 class Z { 
 int i, j; 
public: 
 Z(int ii, int jj); 
 void print(); 
}; 
Z::Z(int ii, int jj) { 
 i = ii; 
 j = jj; 
} 
void Z::print() { 
 cout << "i = " << i << ", j = " << j << endl; 
} 
int main() { 
 Z zz[] = { Z(1,2), Z(3,4), Z(5,6), Z(7,8) }; 
 for(int i = 0; i < sizeof zz / sizeof *zz; i++) 
 zz[i].print(); 
} ///:~ 
Chú ý rằng nó trông như hàm tạo ñược gọi rõ ràng ñối với mỗi ñối tượng 
Hàm tạo mặc ñịnh 
Hàm tạo mặc ñịnh là hàm tạo ñược gọi mà không cần tham số. Ví dụ nếu bạn lấy struct Y ở 
trên và sử dụng nó như thế này: 
 Y y2[2] = { Y(1) }; 
Trình biên dịch sẽ báo rằng không tìm thấy hàm tạo mặc ñịnh. ðối tượng thứ hai trong 
mảng muốn ñược tạo không cần tham số, và lúc ñó trình biên dịch sẽ tìm hàm tạo mặc 
ñịnh. Thật ra, bạn cỏ thể ñịnh nghĩa ñơn giản một mảng ñối tượng 
Y y3[7]; 
Trình biên dịch sẽ báo rằng bạn cần phải có một hàm tạo mặc ñịnh cho mỗi ñối tượng trong 
mảng. 
Vấn ñề tương tự nếu bạn tạo một ñối tượng như vầy: 
Y y4; 
Nhớ rằng, nếu bạn có một hàm tạo, trình biên dịch chắc chắn sẽ gọi hàm tạo ñó, hãy cẩn 
thận trường hợp này. 
Hàm tạo mặc ñịnh sẽ quan trọng khi (và chỉ khi) không có hàm tạo trong cấu trúc (class 
hay struct), khi ñó trình biên dịch sẽ tự ñộng tạo một hàm tạo cho bạn. Vì thế, ñoạn code 
sau vẫn chạy: 
//: C06:AutoDefaultConstructor.cpp 
// Automatically-generated default constructor 
class V { 
 int i; // private 
}; // No constructor 
int main() { 
 V v, v2[10]; 
} ///:~ 
Tuy nhiên, nếu có một hàm tạo nào ñó ñược ñịnh nghĩa, và không có hàm tạo mặc ñịnh, thì 
hiễn nhiên, trường hợp của lớp V ở trên sẽ gây lỗi 
Có thể bạn nghĩ trình biên dịch tạo ra hàm tạo, nên nó sẽ khởi tạo ñối tượng thông minh 
hơn, vd như chĩnh vùng nhơ của tất cả ñối tượng là không. Nhưng ñiều ñó không có – trình 
biên dịch có thể cấp vùng nhờ nhìu hơn nhưng lập trình viên không thể kiểm soát ñược. Nếu 
bạn muốn vùng nhớ ñược tạo là không, bạn phải tự làm ñiều ñó bằng cách viết ra một hàm 
tạo rõ ràng. 
Mặc dù trình biên dịch sẽ tạo hàm tạo mặc ñịnh cho bạn, thường thì hàm tạo mặc ñịnh hiếm 
khi ñúng với những gì bạn muốn. Bạn nên sử dụng tính năng này như một mạng lưới an 
toàn, nhưng ñừng nên lạm dụng nó. Nói chung, bạn nên ñịnh nghĩa rõ ràng hàm tạo chứ 
ñừng nên ñể trình biên dịch tự ñộng tạo hàm tạo nó cho bạn. 
Tóm tắt 
Các cơ chế trong C++ cung cấp cho bạn một gợi ý mạnh về việc tạo và hủy. Khi Stroustrup 
thiết kế C++, một trong những quan sát ñầu tiên của anh về năng suất trong C là phần 
quan trọng trong vấn ñề lập trình gây ra do khởi tạo biến không thích hợp. Những vấn ñề 
này gây ra những lỗi khó phát hiện, và cũng tương tự cho việc hủy không ñúng cách. Bởi vì 
hàm tạo và hàm hủy ñảm bảo cho bạn tính ñúng ñắn trong việc tạo và hủy (trình biên dịch 
sẽ không cho phép bạn tạo và hủy ñối tượng khi hàm tạo và hàm hủy ñược gọi không 
ñúng), bạn sẽ có toàn quyền kiểm soát và an toàn hơn 
Khởi tạo tổng hợp bao gồm nhiều kiểu tương tự - ñiều này giúp bạn ngăn bạn phạm một số 
sai lầm trong tập hợp và làm cho code của bạn gọn gàng hơn 
Vấn ñề an toàn trong C++ là một vấn ñề lớn. Tạo và hủy là một trong những phần ñó, bạn 
có thể tìm hiểu thêm về vấn ñề này trong quyển Thinking in C++ 

File đính kèm:

  • pdfLập trình hướng đối tượng - Chương 6 Tạo và hủy.pdf