Các trường hợp tối ưu hóa codes
- Tạo một đối tượng khi thật sự cần thiết.
- Gọi hàm khởi tạo khi thật sự cần thiết.
- Chỉ định hàm khởi tạo cho các đối tượng là thuộc tính của class.
- Sử dụng tham chiếu, con trỏ (shallow copy) nếu có thể.
Tạo một đối tượng khi thật sự cần thiết
Khi thật sự không cần thiết nên hạn chế khởi tạo 1 đối tượng vì chi phí bao gồm hiệu suất cho khởi tạo và thu hồi rất lớn, bên cạnh đó còn tốn bộ nhớ để lưu trữ.
Xét trường hợp sau
void BeAStdio() { Stdio sObj; ... }
Từ đoạn ...
trở xuống, không hề sử dụng sObj
nên đó là 1 sự lãng phí phải bỏ, tuy nhìn đơn giản nhưng nếu không xóa dòng code đó, khi đến trường hợp code phức tạp hơn như bên dưới:
char* BeAStdio(bool enable) { Stdio sObj; if (enable == true) return nullptr; else ... }
Nếu enable == true
thì hàm sẽ ngưng thực thi và trả nullptr
, sObj
sẽ không có chức năng gì trong hàm, trường hợp này rất lãng phí.
Gọi hàm khởi tạo khi thật sự cần thiết
Mặc định khởi tạo 1 đối tượng, hàm tạo (constructor) được gọi sau khi hàm đó được khởi tạo, giả sử trong hàm khởi tạo có dạng sau:
#include <string.h> typedef char byte; class Proifle { private: byte* m_profilePicture; int m_profilePictureLength; char* m_DNAString; int m_DNAStringLength; public: Profile(int pictureLength, byte* profilePicture, int DNALength, char* DNA) { m_profilePicture = new byte[pictureLength]; m_profilePictureLength = new char[DNALength]; memcpy(m_profilePicture, profilePicture, pictureLength); memcpy(m_DNAString, DNA, DNALength); } }; int main() { /*......*/ StdioMember member(picLength, pic, dnaLength, dna); /*......*/ return 0; }
Từ lúc khởi tạo StdioMember member
thì constructor sẽ gọi ngay sau đó và điều thuận tiện này cũng là điều bất lợi, đôi lúc đó chưa phải là thời điểm muốn khởi tạo các giá trị cho thuộc tính trong đối tượng, có khi thời điểm đó cũng đang cần hiệu suất của máy tính và muốn việc khởi tạo được diễn ra sau đó, không nên đặt các khởi tạo tại constructor.
Nếu chưa cần khởi tạo, có thể sử dụng con trỏ và khi nào cần khởi tạo sẽ cấp phát động bằng toán tử new
.
Nếu bài toán đặt ra là phải cấp phát đối tượng nhưng chưa cần khởi tạo các giá trị trong đối tượng thì cần thiết kế lại lớp như sau.
- Thêm 1 constructor với thân của constructor là rỗng.
- Viết thêm 1 phương thức khởi tạo và gọi phương thức đó khi nào thấy cần thiết.
Code như sau
#include <string.h> typedef char byte; class Profile { private: byte* m_profilePicture; int m_profilePictureLength; char* m_DNAString; int m_DNAStringLength; public: Profile() { } void Init(int pictureLength, byte* profilePicture, int DNALength, char* DNA) { m_profilePicture = new byte[pictureLength]; m_profilePictureLength = new char[DNALength]; memcpy(m_profilePicture, profilePicture, pictureLength); memcpy(m_DNAString, DNA, DNALength); } }; int main() { /*......*/ Profile member; /*......*/ member.Init(); return 0; }
Chỉ định hàm khởi tạo cho các đối tượng là thuộc tính của class
Với 1 thuộc tính trong 1 class, có thể đó là 1 đối tượng, nếu như khai báo thông thường xem như đã gọi constructor mặc định cho nó và không có cách thức để gọi các constructor mong muốn.
class Sins { private: string m_name; public: Sins(string & name) { m_name = name; } }
Phân tích code trên có, 2 quá trình:
Sins
được tạo ra.Sins
gọiconstructor
.
Trong quá trình đối tượng Sins
được tạo thành thì m_name
được tạo, nó sẽ gọi constructor
của nó.
Vào thân hàm constructor
của Sins
và xảy ra copy-constructor gán name
vào m_name
.
Xét riêng m_name
thấy nó phải gọi constructor
trước, sau đó lại tiến hành gán (gọi copy-construtor) thêm 1 lần nữa. Do đó, nên gọi thẳng copy-constructor để tránh thêm bước gọi constructor
.
Để làm điều này, chỉ định copy-constructor cho Sins
khi nó đang khởi tạo bằng cách sau
class Sins { private: string m_name; public: Sins(string & name):m_name(name) { } }
Sử dụng tham chiếu, con trỏ nếu có thể
Với C, chỉ có con trỏ, bản chất khi truyền con trỏ vào 1 hàm là gán địa chỉ của con trỏ, nếu con trỏ đang quản lý 1 đối tượng, không tốn kém việc truyền các đối tượng vào hàm. Đối với cách truyền tham chiếu (trong C++) cũng có ý tưởng tương tự như vậy.
Như hàm vẽ 1 đối tượng tên là Sins
bên dưới, không cần thiết phải sao chép cả đối tượng vì trong trường hợp này chỉ muốn lấy dữ liệu từ nó để vẽ (có thể sử dụng phương pháp truyền địa chỉ qua pointer nếu muốn, nhưng ở đây dùng phương pháp tên chung - truyền tham chiếu).
void DrawSins(const Sins & s, int posX, int posY) { Draw(s.texture, s.rect.x, s.rect.y, s.rect.width, s.height, posX, posY); }