Hàm tạo C++ với tham số con trỏ
Constructor giống như “hàm init”. Họ biến một đống bit tùy ý thành một vật thể sống. Tối thiểu họ khởi tạo các trường được sử dụng nội bộ. Họ cũng có thể phân bổ tài nguyên (bộ nhớ, tệp, semaphores, ổ cắm, v.v.) Show
“ctor” là từ viết tắt điển hình của hàm tạo Có sự khác biệt nào giữa class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 0 và class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 1 không?Một sự khác biệt lớn Giả sử rằng 2 là tên của một số lớp. Sau đó, hàm 3 khai báo một đối tượng 2 cục bộ có tên là 5
Nhưng hàm 6 khai báo một hàm có tên là 7 trả về một 2 0Một hàm tạo của một lớp có thể gọi một hàm tạo khác của cùng lớp để khởi tạo đối tượng class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 9 không?Câu trả lời dưới đây áp dụng cho C++ cổ điển (trước 11 tuổi). bao gồm tính năng C++ 11 của các hàm tạo gọi các hàm tạo cùng loại Không Hãy làm một ví dụ. Giả sử bạn muốn hàm tạo của bạn 20 gọi một hàm tạo khác của cùng một lớp, chẳng hạn như 21, để 21 đó sẽ giúp khởi tạo đối tượng 9. Thật không may, không có cách nào để làm điều này trong Classic C++Một số người làm điều đó anyway. Thật không may, nó không làm những gì họ muốn. Ví dụ, dòng 24 không gọi 21 trên đối tượng 9. Thay vào đó, nó gọi 21 để khởi tạo một đối tượng cục bộ, tạm thời (không phải 9), sau đó nó ngay lập tức hủy đối tượng tạm thời đó khi điều khiển chuyển qua 29 2Đôi khi bạn có thể kết hợp hai hàm tạo thông qua một tham số mặc định 3Nếu điều đó không làm việc, e. g. , nếu không có tham số mặc định phù hợp kết hợp hai hàm tạo, đôi khi bạn có thể chia sẻ mã chung của chúng trong hàm thành viên 40 riêng tư 5BTW KHÔNG cố gắng đạt được điều này thông qua. Một số người nghĩ rằng họ có thể nói 41 trong phần thân của 20. Tuy nhiên đó là xấu, xấu, xấu. Xin đừng viết thư cho tôi và nói với tôi rằng nó dường như hoạt động trên phiên bản cụ thể của trình biên dịch cụ thể của bạn; . Những người xây dựng thực hiện một loạt những điều kỳ diệu nho nhỏ đằng sau hậu trường, nhưng kỹ thuật tồi tệ đó sẽ ảnh hưởng đến những bit được xây dựng một phần đó. Chỉ cần nói khôngCó phải hàm tạo mặc định cho class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 43 luôn là class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 44 không?Không Một “hàm tạo mặc định” là một hàm tạo có thể được gọi mà không có đối số. Một ví dụ về điều này là một hàm tạo không có tham số 0Một ví dụ khác về “hàm tạo mặc định” là một ví dụ có thể nhận các đối số, miễn là chúng được cung cấp các giá trị mặc định 1Hàm tạo nào được gọi khi tôi tạo một mảng gồm các đối tượng class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 43? 43 (ngoại trừ như được thảo luận bên dưới)
Nếu lớp của bạn không có , bạn sẽ gặp lỗi thời gian biên dịch khi cố gắng tạo một mảng bằng cú pháp đơn giản ở trên 2Tuy nhiên, ngay cả khi lớp của bạn đã có một hàm tạo mặc định, bạn nên cố gắng sử dụng chứ không phải là một mảng (). 48 cho phép bạn quyết định sử dụng bất kỳ hàm tạo nào, không chỉ hàm tạo mặc định 4Mặc dù bạn nên sử dụng 48 thay vì một mảng, nhưng đôi khi một mảng có thể là điều nên làm và đối với những trường hợp đó, bạn có thể cần đến cú pháp “khởi tạo mảng rõ ràng”. Đây là cách 00Tất nhiên, bạn không cần phải thực hiện 000 cho mọi mục nhập — bạn có thể nhập bất kỳ số nào bạn muốn, thậm chí cả tham số hoặc các biến khácCuối cùng, để tự khởi tạo các phần tử của mảng. Cảnh báo. no thật la xâu xi. mảng thô không thể thuộc loại 43, vì vậy bạn sẽ cần một loạt các phép con trỏ để thực hiện những việc như tính toán các phép toán chỉ mục mảng. Cảnh báo. nó phụ thuộc vào trình biên dịch và phần cứng. bạn sẽ cần đảm bảo rằng bộ lưu trữ được căn chỉnh với sự căn chỉnh ít nhất nghiêm ngặt như yêu cầu đối với các đối tượng của lớp 43. Cảnh báo. thật tẻ nhạt để làm cho nó an toàn ngoại lệ. bạn sẽ cần hủy các phần tử theo cách thủ công, kể cả trong trường hợp khi một ngoại lệ được ném một phần qua vòng lặp gọi hàm tạo. Nhưng nếu bạn thực sự muốn làm điều đó,. (BTW vị trí-mới là phép thuật được sử dụng bên trong 48. Sự phức tạp của việc làm đúng mọi thứ là một lý do khác để sử dụng 48. )Nhân tiện, tôi đã bao giờ đề cập đến điều đó chưa? Các hàm tạo của tôi có nên sử dụng "danh sách khởi tạo" hoặc "chỉ định" không?danh sách khởi tạo. Trên thực tế, các hàm tạo nên khởi tạo theo quy tắc tất cả các đối tượng thành viên trong danh sách khởi tạo. Một ngoại lệ được thảo luận sâu hơn Xem không gian này để thảo luận về Khởi tạo thành viên dữ liệu không tĩnh trong C++11 01Xem xét hàm tạo sau khởi tạo đối tượng thành viên 006 bằng cách sử dụng danh sách khởi tạo. 007bất cứ điều gì_______1008. Lợi ích phổ biến nhất của việc này là cải thiện hiệu suất. Ví dụ: nếu biểu thức anything cùng loại với biến thành viên 006, thì kết quả của biểu thức anything được tạo trực tiếp bên trong 006 — trình biên dịch không tạo một bản sao riêng của đối tượng. Ngay cả khi các kiểu không giống nhau, trình biên dịch thường có thể thực hiện công việc tốt hơn với các danh sách khởi tạo so với các phép gánCách khác (không hiệu quả) để xây dựng hàm tạo là thông qua phép gán, chẳng hạn như. ________ 1011 sao cũng được ________ 1012. Trong trường hợp này, biểu thức anything sẽ tạo ra một đối tượng tạm thời, riêng biệt và đối tượng tạm thời này được chuyển vào toán tử gán của đối tượng 006. Sau đó, đối tượng tạm thời đó bị hủy tại 29. Điều đó không hiệu quảNhư thể điều đó chưa đủ tệ, còn có một nguyên nhân khác dẫn đến sự kém hiệu quả khi sử dụng phép gán trong hàm tạo. đối tượng thành viên sẽ được xây dựng đầy đủ bởi hàm tạo mặc định của nó và điều này có thể, ví dụ, phân bổ một số lượng bộ nhớ mặc định hoặc mở một số tệp mặc định. Tất cả công việc này có thể là vô ích nếu biểu thức bất kỳ và/hoặc toán tử gán làm cho đối tượng đóng tệp đó và/hoặc giải phóng bộ nhớ đó (e. g. , nếu hàm tạo mặc định không phân bổ vùng bộ nhớ đủ lớn hoặc nếu nó mở sai tệp) Phần kết luận. Tất cả những thứ khác đều như nhau, mã của bạn sẽ chạy nhanh hơn nếu bạn sử dụng danh sách khởi tạo thay vì gán Ghi chú. Không có sự khác biệt về hiệu suất nếu loại 006 là một số loại tích hợp/nội tại, chẳng hạn như 016 hoặc 017 hoặc 018. Nhưng ngay cả trong những trường hợp này, sở thích cá nhân của tôi là đặt các thành viên dữ liệu đó trong danh sách khởi tạo thay vì thông qua chuyển nhượng để thống nhất. Một đối số đối xứng khác có lợi cho việc sử dụng danh sách khởi tạo ngay cả đối với các loại tích hợp/nội tại. 019 không tĩnh và các thành viên dữ liệu tham chiếu không tĩnh không thể được gán một giá trị trong hàm tạo, vì vậy để đối xứng, việc khởi tạo mọi thứ trong danh sách khởi tạo là điều hợp lýBây giờ cho các trường hợp ngoại lệ. Mọi quy tắc đều có ngoại lệ (hmmm; có phải “mọi quy tắc đều có ngoại lệ” có ngoại lệ không? Làm tôi nhớ đến Định lý Bất toàn của Gödel), và có một vài ngoại lệ đối với quy tắc “sử dụng danh sách khởi tạo”. Điểm mấu chốt là sử dụng lẽ thường. nếu nó rẻ hơn, tốt hơn, nhanh hơn, v.v. không sử dụng chúng, thì bằng mọi cách, đừng sử dụng chúng. Điều này có thể xảy ra khi lớp của bạn có hai hàm tạo cần khởi tạo các thành viên dữ liệu của đối tượng 9 theo các thứ tự khác nhau. Hoặc nó có thể xảy ra khi hai thành viên dữ liệu tự tham chiếu. Hoặc khi một thành viên dữ liệu cần tham chiếu đến đối tượng 9 và bạn muốn tránh cảnh báo của trình biên dịch về việc sử dụng từ khóa 9 trước 023 bắt đầu phần thân của hàm tạo (khi trình biên dịch cụ thể của bạn tình cờ đưa ra cảnh báo cụ thể đó). Hoặc khi bạn cần làm (tham số, toàn cầu, v.v. ) trước khi sử dụng biến đó để khởi tạo một trong các thành viên 9 của bạn. Danh sách này không đầy đủ; . Vấn đề chỉ đơn giản là thế này. sử dụng suy nghĩ thông thườngTrình khởi tạo nên được sắp xếp như thế nào trong danh sách khởi tạo của hàm tạo?Các lớp cơ sở ngay lập tức (từ trái sang phải), sau đó là các đối tượng thành viên (từ trên xuống dưới) Nói cách khác, thứ tự của danh sách khởi tạo phải bắt chước thứ tự khởi tạo sẽ diễn ra. Hướng dẫn này không khuyến khích một loại lỗi phụ thuộc đơn hàng đặc biệt tinh vi bằng cách đưa ra manh mối rõ ràng, trực quan. Ví dụ: phần sau chứa một lỗi ghê tởm 02Đầu ra của chương trình này sau 03Lưu ý rằng __________ 1027 được sử dụng (________ 1028) trước khi nó được khởi tạo (________ 1029). Thay vào đó, nếu lập trình viên đã đọc và tuân theo hướng dẫn trong Câu hỏi thường gặp này, thì lỗi sẽ rõ ràng hơn. danh sách khởi tạo của 030 sẽ đọc là 031, cho biết trực quan rằng 027 đã được sử dụng trước khi được khởi tạoKhông phải tất cả các trình biên dịch đều đưa ra thông báo chẩn đoán cho những trường hợp này. Bạn đã được cảnh báo Việc một đối tượng thành viên được khởi tạo bằng cách sử dụng một đối tượng thành viên khác trong biểu thức khởi tạo có hợp lý không?Có, nhưng hãy cẩn thận và chỉ làm điều đó khi nó tăng thêm giá trị Trong danh sách khởi tạo của hàm tạo, cách dễ nhất và an toàn nhất là tránh sử dụng một đối tượng thành viên từ đối tượng 9 trong biểu thức khởi tạo của trình khởi tạo tiếp theo cho đối tượng 9. Hướng dẫn này ngăn cảnDo hướng dẫn này, hàm tạo sau sử dụng 035 thay vì 036, mặc dù chúng tương đương nhau. Tiền tố 037 tránh sự phụ thuộc thứ tự không cần thiết và có thể tránh được 04Một sự phụ thuộc thứ tự không cần thiết vào cách bố trí lớp của 038 và 039 sẽ được đưa ra nếu việc khởi tạo của hàm tạo 039 đã sử dụng 036 thay vì 035. Tuy nhiên, sử dụng 038 trong phần thân hàm tạo ( 044) thì không sao. Không có phụ thuộc thứ tự nào được đưa ra vì toàn bộ danh sách khởi tạo được đảm bảo kết thúc trước khi phần thân của hàm tạo bắt đầu thực thiNếu một đối tượng thành viên phải được khởi tạo bằng một đối tượng thành viên khác thì sao?Nhận xét tuyên bố của các thành viên dữ liệu có hiệu lực với 045Nếu một hàm tạo khởi tạo một đối tượng thành viên của đối tượng 9 bằng cách sử dụng một đối tượng thành viên khác của đối tượng 9,. Ràng buộc bảo trì quan trọng này nên được ghi lại trong phần thân của lớpVí dụ: trong hàm tạo bên dưới, trình khởi tạo cho 039 sử dụng 038 để tránh lệnh gọi thừa tới 050, dẫn đến phụ thuộc thứ tự trong nội dung lớp 05Lưu ý rằng nhận xét 045 được liệt kê với các thành viên dữ liệu bị ảnh hưởng trong nội dung lớp, không phải với danh sách khởi tạo hàm tạo nơi phụ thuộc thứ tự thực sự được tạo. Đó là bởi vì thứ tự của các đối tượng thành viên trong nội dung lớp là rất quan trọng;Bạn có nên sử dụng con trỏ class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 9 trong hàm tạo không?Một số người cảm thấy bạn không nên sử dụng con trỏ 9 trong hàm tạo vì đối tượng chưa được hình thành đầy đủ. Tuy nhiên, bạn có thể sử dụng 9 trong hàm tạo (trong 023body 056 và thậm chí trong ) nếu bạn cẩn thậnĐây là một cái gì đó luôn hoạt động. ____1023body 056 của một hàm tạo (hoặc một hàm được gọi từ hàm tạo) có thể truy cập đáng tin cậy vào các thành viên dữ liệu được khai báo trong một lớp cơ sở và/hoặc các thành viên dữ liệu được khai báo trong lớp riêng của hàm tạo. Điều này là do tất cả các thành viên dữ liệu đó được đảm bảo đã được xây dựng đầy đủ vào thời điểm ____1023body 056 của hàm khởi tạo bắt đầu thực thiĐây là một cái gì đó không bao giờ hoạt động. 023body 056 của hàm tạo (hoặc hàm được gọi từ hàm tạo) không thể xuống lớp dẫn xuất bằng cách gọi hàm thành viên 063 bị ghi đè trong lớp dẫn xuất. Nếu mục tiêu của bạn là truy cập hàm bị ghi đè trong lớp dẫn xuất,. Lưu ý rằng bạn sẽ không nhận được ghi đè trong lớp dẫn xuất độc lập với cách bạn gọi hàm thành viên 063. rõ ràng bằng cách sử dụng con trỏ 9 (e. g. , 066), ngầm sử dụng con trỏ 9 (e. g. , 068) hoặc thậm chí gọi một số hàm khác gọi hàm thành viên 063 trên đối tượng 9 của bạn. Phía dưới là dòng này. ngay cả khi người gọi đang xây dựng một đối tượng của lớp dẫn xuất, trong quá trình khởi tạo của lớp cơ sở,. Bạn đã được cảnh báoĐây là một cái gì đó đôi khi hoạt động. nếu bạn chuyển bất kỳ thành viên dữ liệu nào trong đối tượng 9 sang thành viên dữ liệu khác, bạn phải đảm bảo rằng thành viên dữ liệu khác đã được khởi tạo. Tin vui là bạn có thể xác định xem thành viên dữ liệu khác đã (hoặc chưa) được khởi tạo hay chưa bằng cách sử dụng một số quy tắc ngôn ngữ đơn giản độc lập với trình biên dịch cụ thể mà bạn đang sử dụng. Tin xấu là bạn phải biết những quy tắc ngôn ngữ đó (e. g. , các đối tượng con của lớp cơ sở được khởi tạo trước (tra cứu thứ tự nếu bạn có nhiều và/hoặc kế thừa 063. ), thì các thành viên dữ liệu được định nghĩa trong lớp được khởi tạo theo thứ tự xuất hiện trong khai báo lớp). Nếu bạn không biết các quy tắc này, thì đừng chuyển bất kỳ thành viên dữ liệu nào từ đối tượng 9 (bất kể bạn có sử dụng từ khóa 9 một cách rõ ràng hay không) cho bất kỳ thành viên dữ liệu nào khác. Và nếu bạn biết các quy tắc, hãy cẩn thận"Thành ngữ xây dựng được đặt tên" là gì?Một kỹ thuật cung cấp các thao tác xây dựng trực quan hơn và/hoặc an toàn hơn cho người dùng thuộc lớp của bạn Vấn đề là các hàm tạo luôn có cùng tên với lớp. Do đó, cách duy nhất để phân biệt giữa các hàm tạo khác nhau của một lớp là theo danh sách tham số. Nhưng nếu có nhiều hàm tạo, sự khác biệt giữa chúng trở nên hơi nhỏ và dễ bị lỗi Với Thành ngữ Constructor được đặt tên, bạn khai báo tất cả các hàm tạo của lớp trong phần 075 hoặc 076 và bạn cung cấp các phương thức 077 078 trả về một đối tượng. Các phương thức 078 này được gọi là “Các nhà xây dựng được đặt tên. ” Nói chung, có một phương pháp 078 như vậy cho mỗi cách khác nhau để xây dựng một đối tượngVí dụ: giả sử chúng ta đang xây dựng một lớp 081 đại diện cho một vị trí trên mặt phẳng X-Y. Hóa ra có hai cách phổ biến để chỉ định tọa độ 2 không gian. tọa độ chữ nhật (X+Y), tọa độ cực (Bán kính+Góc). (Đừng lo lắng nếu bạn không thể nhớ những điều này; vấn đề không phải là chi tiết cụ thể của các hệ tọa độ; vấn đề là có một số cách để tạo một đối tượng 081. ) Thật không may, các tham số cho hai hệ tọa độ này giống nhau. hai 018. Điều này sẽ tạo ra một lỗi mơ hồ trong các hàm tạo quá tải 06Một cách để giải quyết sự mơ hồ này là sử dụng Named Constructor Idiom 07Giờ đây, người dùng của 081 có một cú pháp rõ ràng và rõ ràng để tạo các 081 trong cả hai hệ tọa độ 08Đảm bảo rằng các hàm tạo của bạn nằm trong phần 076 nếu bạn muốn 081 có các lớp dẫn xuấtThành ngữ Constructor được đặt tên cũng có thể được sử dụng để Lưu ý rằng Thành ngữ Trình xây dựng được đặt tên, ít nhất là như đã triển khai ở trên, cũng nhanh như việc gọi trực tiếp một hàm tạo — Trả về theo giá trị có nghĩa là có thêm bản sao và thêm chi phí không?Không cần thiết Tất cả (?) trình biên dịch cấp thương mại tối ưu hóa bản sao bổ sung, ít nhất là trong các trường hợp như minh họa trong Để giữ cho ví dụ rõ ràng, hãy loại bỏ mọi thứ xuống những điều cần thiết nhất. Giả sử hàm 089 gọi 090 (“rbv” là viết tắt của “return by value”) trả về một đối tượng 091 theo giá trị 09Bây giờ câu hỏi là, Sẽ có bao nhiêu đối tượng 091? Điểm chính của Câu hỏi thường gặp này là câu trả lời là Không, trình biên dịch C++ cấp thương mại thực hiện trả về theo giá trị theo cách cho phép chúng loại bỏ chi phí hoạt động, ít nhất là trong các trường hợp đơn giản như những trường hợp được trình bày trong Câu hỏi thường gặp trước đó. Đặc biệt, tất cả (?) trình biên dịch C++ cấp thương mại sẽ tối ưu hóa trường hợp này 20Chắc chắn trình biên dịch được phép tạo một đối tượng 091 cục bộ, tạm thời, sau đó sao chép-xây dựng đối tượng tạm thời đó vào biến 5 trong 089, sau đó hủy đối tượng tạm thời đó. Nhưng tất cả (?) trình biên dịch C++ cấp thương mại sẽ không làm điều đó. câu lệnh 099 sẽ trực tiếp xây dựng chính 5. Không phải là bản sao của 5, không phải là con trỏ tới 5, không phải là tham chiếu tới 5, mà chính là 5Bạn có thể dừng ở đây nếu bạn không thực sự muốn hiểu đoạn trước, nhưng nếu bạn muốn biết điều bí mật (ví dụ, để bạn có thể dự đoán một cách đáng tin cậy khi trình biên dịch có thể và không thể cung cấp sự tối ưu hóa đó cho bạn), thì . Khi ________ 1089 gọi ________ 1090, trình biên dịch sẽ bí mật chuyển một con trỏ đến vị trí mà _________ 1090 được cho là sẽ xây dựng đối tượng “được trả lại”. Nó có thể trông giống như thế này (nó được hiển thị dưới dạng 208 thay vì 209 vì đối tượng 091 chưa được tạo) 21Vì vậy, thành phần đầu tiên trong nước sốt bí mật là trình biên dịch (thường) chuyển đổi giá trị trả về thành chuyển qua con trỏ. Điều này có nghĩa là các trình biên dịch cấp thương mại không bận tâm đến việc tạo một. họ trực tiếp xây dựng đối tượng được trả về ở vị trí được chỉ ra bởi 211Thành phần thứ hai trong nước sốt bí mật là các trình biên dịch thường triển khai các hàm tạo bằng cách sử dụng một kỹ thuật tương tự. Điều này phụ thuộc vào trình biên dịch và hơi lý tưởng hóa (tôi cố ý bỏ qua cách xử lý 088 và quá tải), nhưng trình biên dịch thường triển khai 213 bằng cách sử dụng một cái gì đó như thế này 22Đặt những thứ này lại với nhau, trình biên dịch có thể triển khai câu lệnh 099 trong 090 bằng cách chuyển 211 làm con trỏ 9 của hàm tạo 23Vì vậy, 089 chuyển 219 đến 090 và lần lượt 090 chuyển 219 đến hàm tạo (dưới dạng con trỏ 9). Điều đó có nghĩa là nhà xây dựng trực tiếp xây dựng 5Vào đầu những năm 90, tôi đã tổ chức một cuộc hội thảo cho nhóm trình biên dịch của IBM ở Toronto, và một trong số các kỹ sư của họ đã nói với tôi rằng họ nhận thấy việc tối ưu hóa giá trị trả về này nhanh đến mức bạn có thể nhận được nó ngay cả khi bạn không biên dịch với tính năng tối ưu hóa đã được bật. . Bởi vì tối ưu hóa trả về theo giá trị làm cho trình biên dịch tạo ra ít mã hơn, nên nó thực sự cải thiện thời gian biên dịch ngoài việc làm cho mã được tạo của bạn nhỏ hơn và nhanh hơn. Vấn đề là tối ưu hóa lợi nhuận theo giá trị hầu như được triển khai phổ biến, ít nhất là trong các trường hợp mã như trường hợp được hiển thị ở trên suy nghĩ cuối cùng. cuộc thảo luận này chỉ giới hạn ở việc liệu có bất kỳ bản sao bổ sung nào của đối tượng được trả về trong lệnh gọi trả về theo giá trị hay không. Đừng nhầm lẫn điều đó với những thứ khác có thể xảy ra trong 089. Ví dụ: nếu bạn thay đổi 089 từ 227 thành 228 229 (lưu ý 29 sau phần khai báo), trình biên dịch bắt buộc phải sử dụng toán tử gán của 091 và trừ khi trình biên dịch có thể chứng minh rằng hàm tạo mặc định của 091 theo sau là toán tử gán chính xác như . Tối ưu hóa trả về theo giá trị vẫn đóng vai trò của nó vì sẽ chỉ có một giá trị tạm thời, nhưng bằng cách thay đổi 227 thành 228 229, bạn đã ngăn trình biên dịch loại bỏ giá trị tạm thời cuối cùng đó.Còn việc trả về một biến cục bộ theo giá trị thì sao?Khi mã của bạn trả về một biến cục bộ theo giá trị, trình biên dịch của bạn có thể tối ưu hóa hoàn toàn biến cục bộ - chi phí không gian bằng không và chi phí thời gian bằng không - biến cục bộ không bao giờ thực sự tồn tại như một đối tượng riêng biệt với biến mục tiêu của người gọi (xem bên dưới để biết chi tiết cụ thể . Các trình biên dịch khác không tối ưu hóa nó đi Có vài(. ) của các trình biên dịch tối ưu hóa hoàn toàn biến cục bộ
Có vài(. ) của các trình biên dịch không tối ưu hóa biến cục bộ
Đây là một ví dụ cho thấy những gì chúng tôi muốn nói trong Câu hỏi thường gặp này 24Câu hỏi được giải quyết trong Câu hỏi thường gặp này là. Có bao nhiêu đối tượng 091 thực sự được tạo trong hệ thống thời gian chạy? . tạm thời được tạo bởi 239, biến 240 (trong 090) và biến 5 (trong 089). Tuy nhiên, như chúng ta đã thấy trước đó vào cùng một đối tượng, giảm tổng số đối tượng từ 3 xuống 2. Nhưng Câu hỏi thường gặp này đẩy nó thêm một bước nữa. 240 (trong 090) có hiển thị dưới dạng một đối tượng thời gian chạy riêng biệt từ 5 (trong 089) không?Một số trình biên dịch, bao gồm nhưng không giới hạn ở những trình biên dịch được liệt kê ở trên, tối ưu hóa hoàn toàn biến cục bộ 240. Trong các trình biên dịch đó, chỉ có một đối tượng 091 trong đoạn mã trên. Biến 5 của 089 hoàn toàn giống hệt đối tượng với biến của 090 là 240Họ làm điều này theo cách tương tự như được mô tả. giá trị trả về trong hàm 090 được triển khai dưới dạng truyền qua con trỏ, trong đó con trỏ trỏ đến vị trí mà đối tượng được trả về sẽ được khởi tạoVì vậy, thay vì xây dựng 240 như một đối tượng cục bộ, các trình biên dịch này chỉ cần xây dựng 258 và mỗi khi chúng thấy biến 240 được sử dụng trong mã nguồn ban đầu, chúng sẽ thay thế bằng 258. Sau đó, dòng 261 trở thành đơn giản là 262 vì đối tượng được trả về đã được tạo ở vị trí được chỉ định bởi người gọiĐây là mã kết quả (giả) 25báo trước. tối ưu hóa này chỉ có thể được áp dụng khi tất cả các câu lệnh 099 của hàm trả về cùng một biến cục bộ. Nếu một câu lệnh 099 trong 090 trả về biến cục bộ 240 nhưng một câu lệnh khác trả về một thứ khác, chẳng hạn như toàn cầu hoặc tạm thời, trình biên dịch không thể đặt bí danh biến cục bộ thành đích của người gọi, 5. Việc xác minh rằng tất cả các câu lệnh trả về của hàm trả về cùng một biến cục bộ đòi hỏi người viết trình biên dịch phải làm thêm công việc, đó thường là lý do tại sao một số trình biên dịch không thực hiện được việc tối ưu hóa trả về cục bộ theo giá trị đósuy nghĩ cuối cùng. cuộc thảo luận này chỉ giới hạn ở việc liệu có bất kỳ bản sao bổ sung nào của đối tượng được trả về trong lệnh gọi trả về theo giá trị hay không. Đừng nhầm lẫn điều đó với những thứ khác có thể xảy ra trong 089. Ví dụ: nếu bạn thay đổi 089 từ 227 thành 228 229 (lưu ý 29 sau phần khai báo), trình biên dịch bắt buộc phải sử dụng toán tử gán của 091 và trừ khi trình biên dịch có thể chứng minh rằng hàm tạo mặc định của 091 theo sau là toán tử gán chính xác như . Tối ưu hóa trả về theo giá trị vẫn đóng vai trò của nó vì sẽ chỉ có một giá trị tạm thời, nhưng bằng cách thay đổi 227 thành 228 229, bạn đã ngăn trình biên dịch loại bỏ giá trị tạm thời cuối cùng đó.Tại sao tôi không thể khởi tạo dữ liệu thành viên class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 078 của mình trong danh sách khởi tạo của nhà xây dựng?Bởi vì bạn phải xác định rõ ràng các thành viên dữ liệu 078 của lớp mình 283 26 284 (hoặc 285 hoặc bất cứ thứ gì) 27Ghi chú. trong một số trường hợp, định nghĩa của 286 có thể không chứa phần khởi tạo 287. Để biết chi tiết, xem vàTại sao các lớp có thành viên dữ liệu class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 078 gặp lỗi liên kết?Bởi vì. Nếu bạn không làm điều này, có thể bạn sẽ gặp lỗi trình liên kết 290. Ví dụ 28Trình liên kết sẽ hét vào mặt bạn ( 291) trừ khi bạn xác định (trái ngược với chỉ khai báo) 286 trong (chính xác) một trong các tệp nguồn của bạn 29Vị trí thông thường để xác định thành viên dữ liệu 078 của 294 43 là tệp 284 (hoặc 285 hoặc bất kỳ phần mở rộng tệp nguồn nào bạn sử dụng)Ghi chú. , tuy nhiên nếu bạn từng sử dụng thành viên dữ liệu, bạn vẫn cần xác định rõ ràng nó trong chính xác một đơn vị biên dịch. Trong trường hợp này, bạn không bao gồm trình khởi tạo 287 trong định nghĩa. bao gồm chủ đề nàyTôi có thể thêm bộ khởi tạo class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 287class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 29 vào phần khai báo của thành viên dữ liệu class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 078 class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 019 ở phạm vi lớp không?Có, mặc dù với một số lưu ý quan trọng Trước khi xem qua các cảnh báo, đây là một ví dụ đơn giản được phép 30Và, , nó phải được định nghĩa chính xác trong một đơn vị biên dịch, mặc dù lần này không có phần khởi tạo 287 31Thông báo trước là bạn chỉ có thể thực hiện việc này với các kiểu tích phân hoặc kiểu liệt kê và biểu thức khởi tạo phải là một biểu thức có thể được đánh giá tại thời điểm biên dịch. nó chỉ được chứa các hằng số khác, có thể được kết hợp với các toán tử tích hợp. Ví dụ: 308 là biểu thức hằng số thời gian biên dịch, cũng như 309 miễn là 310 và 311 là hằng số thời gian biên dịch. Sau phần khai báo trên, 312 cũng là một hằng số thời gian biên dịch. nó có thể được sử dụng trong các biểu thức hằng số thời gian biên dịch khácNếu bạn đã từng lấy địa chỉ của 312, chẳng hạn như chuyển nó theo tham chiếu hoặc nói rõ ràng là 314, trình biên dịch sẽ đảm bảo rằng nó có một địa chỉ duy nhất. Nếu không, 312 thậm chí sẽ không chiếm dung lượng trong vùng dữ liệu tĩnh của quy trình của bạn"Lệnh khởi tạo ____1078 'thất bại' (sự cố)" là gì?Một cách tinh vi để làm hỏng chương trình của bạn Vấn đề thứ tự khởi tạo 078 là một khía cạnh rất tế nhị và thường bị hiểu sai của C++. Thật không may, nó rất khó phát hiện — lỗi thường xảy ra trước khi 318 bắt đầuTóm lại, giả sử bạn có hai đối tượng 078 5 và 240 tồn tại trong các tệp nguồn riêng biệt, chẳng hạn như 322 và 323. Giả sử thêm rằng việc khởi tạo đối tượng 240 (thường là hàm tạo của đối tượng 240) gọi một số phương thức trên đối tượng 5Đó là nó. Nó đơn giản mà Phần khó khăn là bạn có 50% -50% cơ hội làm hỏng chương trình. Nếu đơn vị biên dịch cho 322 tình cờ được khởi tạo trước, thì tất cả đều ổn. Nhưng nếu đơn vị biên dịch cho 323 được khởi tạo trước, thì quá trình khởi tạo của 240 sẽ được chạy trước quá trình khởi tạo của 5 và bạn đã nâng cốc chúc mừng. e. g. , Hàm tạo của 240 có thể gọi một phương thức trên đối tượng 5, nhưng đối tượng 5 vẫn chưa được tạoĐể biết cách giải quyết vấn đề, hãy xem Ghi chú. Vấn đề thứ tự khởi tạo tĩnh cũng có thể áp dụng cho các loại tích hợp/nội tại Làm cách nào để ngăn chặn “sự cố thứ tự khởi tạo ____1078”?Để ngăn , hãy sử dụng Thành ngữ Xây dựng khi sử dụng lần đầu, được mô tả bên dưới Ý tưởng cơ bản của Thành ngữ Xây dựng khi sử dụng lần đầu là bọc đối tượng 078 của bạn bên trong một hàm. Ví dụ: giả sử bạn có hai lớp, 43 và 337. Có một đối tượng namespace-scope/global 43 được gọi là 5, và một namespace-scope/global 337 object được gọi là 240. Hàm tạo của 337 gọi phương thức 343 trên đối tượng 5. Tệp 322 định nghĩa đối tượng 5 32Tệp 323 định nghĩa đối tượng 240 33Để hoàn thiện, hàm tạo 337 có thể trông giống như thế này 34Bạn sẽ có một nếu 240 được xây dựng trước 5. Như đã viết ở trên, thảm họa này sẽ xảy ra khoảng 50% thời gian, vì hai đối tượng được khai báo trong các tệp nguồn khác nhau và các tệp nguồn đó không đưa ra gợi ý nào cho trình biên dịch hoặc trình liên kết về thứ tự khởi tạo tĩnhCó nhiều giải pháp cho vấn đề này, nhưng một giải pháp rất đơn giản và hoàn toàn di động là Thành ngữ Xây dựng khi sử dụng lần đầu. thay thế đối tượng namespace-scope/global 43 5 bằng namespace-scope/hàm toàn cầu 7 trả về đối tượng 43 theo tham chiếu 35Vì các đối tượng cục bộ 078 được xây dựng lần đầu tiên luồng điều khiển qua khai báo của chúng (chỉ), nên câu lệnh 358 ở trên sẽ chỉ xảy ra một lần. lần đầu tiên 7 được gọi. Mọi cuộc gọi tiếp theo sẽ trả về cùng một đối tượng 43 (đối tượng được chỉ định bởi 361). Sau đó, tất cả những gì bạn làm là thay đổi cách sử dụng của bạn từ 5 thành 7 36Đây được gọi là Thành ngữ xây dựng khi sử dụng lần đầu bởi vì nó chỉ làm được điều đó. đối tượng (phạm vi không gian tên hợp lý / toàn cầu) 43 được xây dựng trong lần sử dụng đầu tiênNhược điểm của phương pháp này là đối tượng 43 không bao giờ bị hủy. Nếu đối tượng 43 có một hàm hủy với các tác dụng phụ quan trọng, thì đó là câu trả lời cho mối quan tâm này; Ghi chú. Vấn đề thứ tự khởi tạo tĩnh cũng có thể áp dụng cho các loại tích hợp/nội tại Tại sao Thành ngữ Xây dựng khi sử dụng lần đầu không sử dụng đối tượng class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 078 thay vì con trỏ class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 078?Câu trả lời ngắn. có thể sử dụng một đối tượng tĩnh, nhưng làm như vậy sẽ mở ra một vấn đề khác (tinh vi không kém, khó chịu không kém) Câu trả lời dài. đôi khi mọi người lo lắng về thực tế là “rò rỉ. ” Trong nhiều trường hợp, đây không phải là vấn đề, nhưng nó lại là vấn đề trong một số trường hợp. Ghi chú. mặc dù đối tượng được chỉ ra bởi 361 trong Câu hỏi thường gặp trước đó không bao giờ bị xóa, bộ nhớ không thực sự bị "rò rỉ" khi chương trình thoát do hệ điều hành tự động lấy lại tất cả bộ nhớ trong đống của chương trình khi chương trình đó thoát. Nói cách khác, lần duy nhất bạn cần lo lắng về điều này là khi hàm hủy của đối tượng 43 thực hiện một số hành động quan trọng (chẳng hạn như ghi nội dung nào đó vào tệp) đôi khi phải xảy ra trong khi chương trình đang thoátTrong những trường hợp mà đối tượng xây dựng trên lần sử dụng đầu tiên (trong trường hợp này là 43) cuối cùng cần phải bị hủy, bạn có thể cân nhắc thay đổi chức năng 7 như sau 37Tuy nhiên, có (hay đúng hơn là có thể có) một vấn đề khá tế nhị với sự thay đổi này. Để hiểu vấn đề tiềm ẩn này, hãy nhớ tại sao chúng ta lại làm tất cả những điều này ngay từ đầu. chúng tôi cần đảm bảo 100% đối tượng tĩnh của chúng tôi (a) được xây dựng trước lần sử dụng đầu tiên và (b) không bị hủy cho đến sau lần sử dụng cuối cùng. Rõ ràng sẽ là một thảm họa nếu bất kỳ đối tượng tĩnh nào được sử dụng trước khi xây dựng hoặc sau khi phá hủy. Thông báo ở đây là bạn cần phải lo lắng về hai tình huống (khởi tạo tĩnh và khử khởi tạo tĩnh), không chỉ một Bằng cách thay đổi khai báo từ 373 thành 374, chúng tôi vẫn xử lý chính xác tình huống khởi tạo nhưng chúng tôi không còn xử lý tình huống khử khởi tạo nữa. Ví dụ: nếu có 3 đối tượng tĩnh, chẳng hạn như 310, 311 và 377, sử dụng 361 trong quá trình hủy của chúng, thì cách duy nhất để tránh thảm họa khử khởi tạo tĩnh là nếu 361 bị hủy sau cả ba đối tượngđiểm là đơn giản. nếu có bất kỳ đối tượng tĩnh nào khác mà trình hủy của chúng có thể sử dụng 361 sau khi 361 bị hủy, bang, bạn chết. Nếu các hàm tạo của 310, 311 và 377 sử dụng 361, thông thường bạn sẽ ổn vì hệ thống thời gian chạy sẽ, trong quá trình khử khởi tạo tĩnh, hủy 361 sau khi đối tượng cuối cùng trong số ba đối tượng đó bị hủy. Tuy nhiên, nếu 310 và/hoặc 311 và/hoặc 377 không sử dụng được 361 trong hàm tạo của chúng và/hoặc nếu bất kỳ mã nào ở bất kỳ đâu lấy được địa chỉ của 361 và trao nó cho một số đối tượng tĩnh khác, thì mọi khả năng đặt cược đều bị tắt và bạn phải rất, Có, nhưng nó có các chi phí không nhỏ khác Kỹ thuật đảm bảo cả khởi tạo class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 078 và khử khởi tạo class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 078 là gì?Câu trả lời ngắn. sử dụng Thành ngữ Bộ đếm tiện lợi (nhưng đảm bảo rằng bạn hiểu sự đánh đổi không tầm thường. ) Động lực
LÀM. VIẾT NÀY LÊN LÀM. VIẾT LÊN CÁC GIAO DỊCH - bây giờ bạn đã biết cách sử dụng Thành ngữ Bộ đếm tiện lợi, hãy chắc chắn rằng bạn hiểu cả thời điểm và (đặc biệt là. ) khi không sử dụng. Một kích thước không phù hợp với tất cả Làm cách nào để ngăn chặn “sự cố thứ tự khởi tạo class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 078” cho các thành viên dữ liệu class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 078 của tôi?Sử dụng Thành ngữ cấu trúc khi sử dụng lần đầu, về cơ bản giống như , hoặc có lẽ , nhưng nó sử dụng hàm thành viên 078 thay vì hàm phạm vi không gian tên/hàm toàn cầuGiả sử bạn có một lớp 397 có một đối tượng 078 43 38Đương nhiên, thành viên 078 này được khởi tạo riêng 39Đương nhiên, đối tượng 43 cũng sẽ được sử dụng trong một hoặc nhiều phương thức của 397 50Nhưng bây giờ “kịch bản thảm họa” là nếu ai đó ở đâu đó gọi phương thức này trước khi đối tượng 43 được xây dựng. Ví dụ: nếu người khác tạo một đối tượng tĩnh 397 và gọi phương thức 505 của nó trong quá trình khởi tạo 078, thì bạn sẽ phụ thuộc vào trình biên dịch về việc liệu trình biên dịch sẽ xây dựng 507 trước hay sau khi 505 được gọi. (Lưu ý rằng ủy ban ANSI/ISO C++ đang giải quyết vấn đề này, nhưng trình biên dịch vẫn chưa có sẵn để xử lý những thay đổi này; hãy theo dõi không gian này để cập nhật trong tương lai. )Trong mọi trường hợp, việc thay đổi thành viên dữ liệu 507 078 thành hàm thành viên 078 luôn có thể di động và an toàn 51Đương nhiên, thành viên 078 này được khởi tạo riêng 52Sau đó, bạn chỉ cần thay đổi bất kỳ cách sử dụng nào của 006 thành 7 53Nếu bạn siêu nhạy cảm với hiệu suất và bạn lo lắng về chi phí hoạt động của một lệnh gọi chức năng bổ sung trên mỗi lệnh gọi của 515, bạn có thể thiết lập một 078 517 để thay thế. Như bạn nhớ lại, 078 cục bộ chỉ được khởi tạo một lần (lần đầu tiên kiểm soát chảy qua khai báo của chúng), vì vậy điều này sẽ chỉ gọi 519 một lần. lần đầu tiên 515 được gọi 54Ghi chú. Vấn đề thứ tự khởi tạo tĩnh cũng có thể áp dụng cho các loại tích hợp/nội tại Tôi có cần lo lắng về “vấn đề thứ tự khởi tạo class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 078” đối với các biến thuộc loại tích hợp/nội tại không?Đúng Nếu bạn khởi tạo kiểu tích hợp/nội tại của mình bằng cách sử dụng lệnh gọi hàm, vấn đề thứ tự khởi tạo tĩnh có thể giết bạn cũng tệ như với các kiểu do người dùng định nghĩa/lớp. Ví dụ: đoạn mã sau hiển thị lỗi 55Đầu ra của chương trình nhỏ này sẽ cho thấy rằng nó sử dụng 240 trước khi khởi tạo nó. Giải pháp, như trước đây, là 56Tất nhiên, bạn có thể đơn giản hóa việc này bằng cách di chuyển mã khởi tạo cho 5 và 240 vào các chức năng tương ứng của chúng 57Và, nếu bạn có thể loại bỏ các câu lệnh in, bạn có thể đơn giản hóa chúng thành một thứ thực sự đơn giản 58Hơn nữa, vì 240 được khởi tạo bằng một biểu thức hằng, nên nó không còn cần hàm bao bọc nữa — nó có thể lại là một biến đơn giảnLàm cách nào tôi có thể xử lý một hàm tạo không thành công?Ném một ngoại lệ. Để biết chi tiết, xem "Thành ngữ tham số được đặt tên" là gì?Đó là một cách khá hữu ích để khai thác Vấn đề cơ bản được giải quyết bởi Thành ngữ tham số được đặt tên là C++ chỉ hỗ trợ các tham số vị trí. Ví dụ: người gọi hàm không được phép nói, “Đây là giá trị cho tham số chính thức 526 và thứ khác này là giá trị cho tham số chính thức 527. ” Tất cả những gì bạn có thể làm trong C++ (và C và Java) là nói, “Đây là tham số đầu tiên, đây là tham số thứ hai, v.v. ” Phương án thay thế, được gọi là các tham số được đặt tên và được triển khai bằng ngôn ngữ Ada, đặc biệt hữu ích nếu một hàm nhận một số lượng lớn các tham số hầu hết có thể mặc địnhTrong nhiều năm, mọi người đã đưa ra rất nhiều cách giải quyết cho việc thiếu các tham số được đặt tên trong C và C++. Một trong số đó liên quan đến việc chôn các giá trị tham số trong một tham số chuỗi sau đó phân tích cú pháp chuỗi này trong thời gian chạy. Đây là những gì được thực hiện trong tham số thứ hai của 528, ví dụ. Một cách giải quyết khác là kết hợp tất cả các tham số boolean trong một bản đồ bit, sau đó hàm gọi hoặc một loạt các hằng số được dịch chuyển bit lại với nhau để tạo ra tham số thực tế. Đây là những gì được thực hiện trong tham số thứ hai của 529, ví dụ. Những cách tiếp cận này hoạt động, nhưng kỹ thuật sau đây tạo ra mã người gọi rõ ràng hơn, dễ viết hơn, dễ đọc hơn và nói chung là thanh lịch hơnÝ tưởng, được gọi là Thành ngữ tham số được đặt tên, là thay đổi các tham số của hàm thành các phương thức của một lớp mới được tạo, trong đó tất cả các phương thức này trả về 530 theo tham chiếu. Sau đó, bạn chỉ cần đổi tên hàm chính thành một phương thức “do-it” không tham số trên lớp đóChúng tôi sẽ làm một ví dụ để làm cho đoạn trước dễ hiểu hơn Ví dụ sẽ dành cho khái niệm “mở tệp”. Giả sử khái niệm đó yêu cầu một cách hợp lý một tham số cho tên của tệp và tùy ý cho phép các tham số để biết liệu tệp có nên được mở ở chế độ chỉ đọc hay không. đọc ghi vs. chỉ ghi, tệp có nên được tạo hay không nếu nó chưa tồn tại, vị trí ghi nên ở cuối (“chắp thêm”) hay ở đầu (“ghi đè”), kích thước khối nếu tệp . quyền truy cập độc quyền và có thể là một số quyền truy cập khác. Nếu chúng tôi triển khai khái niệm này bằng cách sử dụng một hàm bình thường với các tham số vị trí, mã người gọi sẽ rất khó đọc. sẽ có tới 8 tham số vị trí và người gọi có thể sẽ mắc nhiều lỗi. Vì vậy, thay vào đó chúng tôi sử dụng Thành ngữ tham số được đặt tên Trước khi chúng tôi thực hiện triển khai, đây là mã trình gọi có thể trông như thế nào, giả sử bạn sẵn sàng chấp nhận tất cả các tham số mặc định của hàm 59Đó là trường hợp dễ dàng. Bây giờ, đây là giao diện nếu bạn muốn thay đổi một loạt các tham số 00Lưu ý cách các “tham số”, nếu gọi chúng như vậy là hợp lý, sắp xếp theo thứ tự ngẫu nhiên (chúng không theo vị trí) và tất cả chúng đều có tên. Vì vậy, lập trình viên không cần phải nhớ thứ tự của các tham số và tên (hy vọng) rõ ràng Vì vậy, đây là cách thực hiện nó. trước tiên, chúng tôi tạo một lớp ( 531) chứa tất cả các giá trị tham số dưới dạng thành viên dữ liệu 075. Các tham số bắt buộc (trong trường hợp này, tham số bắt buộc duy nhất là tên tệp) được triển khai như một tham số vị trí bình thường trên hàm tạo của 531, nhưng hàm tạo đó không thực sự mở tệp. Sau đó, tất cả các tham số tùy chọn (chỉ đọc so với. đọc ghi, v.v. ) trở thành các phương thức. Các phương pháp này (e. g. , 534, 535, v.v. ) trả về một tham chiếu đến đối tượng 9 của chúng để các lệnh gọi phương thức có thể được 01Điều khác duy nhất cần làm là tạo hàm tạo cho lớp 537 để lấy một đối tượng 531 02Hàm tạo này nhận các tham số thực tế từ đối tượng OpenFile, sau đó thực sự mở tệp 03Lưu ý rằng 531 tuyên bố 537 là 541 của nó, theo cách đóVì mỗi hàm thành viên trong chuỗi trả về một tham chiếu nên không có sự sao chép đối tượng và chuỗi có hiệu quả cao. Hơn nữa, nếu các hàm thành viên khác nhau là 544, thì mã đối tượng được tạo có thể sẽ ngang bằng với mã kiểu C đặt các thành viên khác nhau của một 545. Tất nhiên, nếu các chức năng thành viên không phải là 544, thì có thể có một chút tăng kích thước mã và giảm một chút hiệu suất (nhưng chỉ khi quá trình xây dựng xảy ra trên đường dẫn quan trọng của chương trình gắn với CPU; đây là một hộp sâu I Tại sao tôi gặp lỗi sau khi khai báo đối tượng class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 091 qua class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 548?Bởi vì điều đó không tạo ra một đối tượng 091 - nó khai báo một hàm không phải thành viên trả về một đối tượng 091. Thuật ngữ “Most Vexing Parse” được đặt ra bởi Scott Myers để mô tả tình huống nàyĐiều này thực sự sẽ gây tổn thương; Đầu tiên, đây là một lời giải thích tốt hơn về vấn đề. Giả sử có một lớp tên là 551 có một ctor mặc định. Đây thậm chí có thể là một lớp thư viện chẳng hạn như 552, nhưng bây giờ chúng ta sẽ chỉ gọi nó là 551 04Bây giờ, giả sử có một lớp khác tên là 091 có một diễn viên lấy một 551. Như trước đây, điều này có thể được xác định bởi một người khác ngoài bạn 05Bây giờ bạn muốn tạo một đối tượng 091 bằng cách sử dụng một 551 tạm thời. Nói cách khác, bạn muốn tạo một đối tượng thông qua 558 và chuyển đối tượng đó tới ctor 091 để tạo một đối tượng 091 cục bộ có tên là 5 06Đó là một câu chuyện dài, nhưng có một giải pháp (hy vọng bạn đang ngồi xuống. ) là thêm một cặp 562 xung quanh phần 558 07Một giải pháp khác là sử dụng 287 trong phần khai báo của bạn (xem bản in đẹp bên dưới) 08Ghi chú. Giải pháp trên yêu cầu 565 để có thể truy cập hàm tạo bản sao 091. Trong hầu hết các tình huống, điều đó có nghĩa là trình tạo bản sao của 091 cần phải là 077, mặc dù nó không cần phải là 077 trong trường hợp ít phổ biến hơn khi 565 là bạn của 571. Nếu bạn không chắc điều đó có nghĩa là gì, hãy thử. nếu mã của bạn biên dịch, bạn đã vượt qua bài kiểm traĐây là một giải pháp khác (bản in đẹp hơn bên dưới) 09Ghi chú. Từ "thường" ở trên có nghĩa là điều này. điều trên chỉ thất bại khi 572 hoặc khi không thể truy cập trình tạo bản sao của 091 (thường khi đó là 075 hoặc 076 và mã của bạn không phải là 541). Nếu bạn không chắc điều đó có nghĩa là gì, hãy dành 60 giây và biên dịch nó. Bạn được đảm bảo tìm hiểu xem nó có hoạt động hay không trong thời gian biên dịch, vì vậy nếu nó được biên dịch rõ ràng, nó sẽ hoạt động trong thời gian chạyTuy nhiên, giải pháp tốt nhất, việc tạo ra giải pháp này ít nhất được thúc đẩy một phần bởi thực tế là Câu hỏi thường gặp này tồn tại, là sử dụng , thay thế 562 xung quanh cuộc gọi 558 bằng 580 thay thế 10Đó là kết thúc của các giải pháp; . Khi trình biên dịch nhìn thấy 548, nó nghĩ rằng phần 558 đang khai báo một hàm không phải thành viên trả về một đối tượng 551, vì vậy nó nghĩ rằng bạn đang khai báo sự tồn tại của một hàm gọi là 5 trả về một 091 và nhận một tham số duy nhất của . ”Bây giờ đây là phần buồn. Trên thực tế, nó thật thảm hại. Một số máy bay không người lái không suy nghĩ ngoài kia sẽ bỏ qua đoạn cuối cùng đó, sau đó họ sẽ áp đặt một tiêu chuẩn viết mã ngu ngốc, không chính xác, không liên quan và đơn giản có nội dung như: “Không bao giờ tạo tạm thời bằng cách sử dụng hàm tạo mặc định” hoặc “Luôn luôn . Nếu đó là bạn, hãy tự bắn mình trước khi gây thêm sát thương. Những người không hiểu vấn đề không nên nói cho người khác cách giải quyết. harp (Đó chủ yếu là lưỡi trong má. Nhưng có một hạt sự thật trong đó. Vấn đề thực sự là mọi người có xu hướng tôn thờ sự nhất quán và họ có xu hướng ngoại suy từ những điều tối nghĩa đến những điều phổ biến. Điều đó không khôn ngoan. ) Mục đích của từ khóa class Fred { public: Fred(); // ... }; int main() { Fred a[10]; // Calls the default constructor 10 times Fred* p = new Fred[10]; // Calls the default constructor 10 times // ... } 573 là gì?Từ khóa 573 là một trang trí tùy chọn cho các hàm tạo và toán tử chuyển đổi để báo cho trình biên dịch biết rằng một hàm tạo hoặc toán tử chuyển đổi nhất định có thể không được sử dụng để chuyển một biểu thức sang loại lớp của nóVí dụ: không có từ khóa 573, đoạn mã sau là hợp lệ 11Nhưng đôi khi bạn muốn ngăn chặn loại quảng cáo ngầm hoặc chuyển đổi loại ngầm này. Ví dụ: nếu 091 thực sự là một vùng chứa dạng mảng và 42 là kích thước ban đầu, bạn có thể muốn cho phép người dùng của mình nói, 592 hoặc có thể là 593, nhưng không chỉ 594. Nếu đúng như vậy, bạn nên sử dụng từ khóa 573 12Bạn có thể kết hợp các hàm tạo 573 và không phải ____2573 và các toán tử chuyển đổi trong cùng một lớp. Ví dụ: lớp này có một hàm tạo 573 lấy một 599 nhưng một hàm tạo không phải 573 lấy một 001 và có thể được chuyển đổi hoàn toàn thành gấp đôi, nhưng chỉ được chuyển đổi rõ ràng thành bool 13Đoạn mã trên sẽ in như sau 14Biến 310 được khởi tạo bằng cách sử dụng hàm tạo 003 vì không thể sử dụng 004 trong cách truyền ngầm định, nhưng 005 có thể được hiểu là một 006, nghĩa là, như 007 và được chuyển đổi ngầm định sang 091 bằng cách sử dụng 009. Đây có thể hoặc không phải là những gì bạn dự định, nhưng đây là những gì xảy raTại sao hàm tạo của tôi không hoạt động bình thường?Đây là một câu hỏi có nhiều dạng. Như là
Theo mặc định, một lớp được cung cấp một hàm tạo sao chép và một phép gán sao chép sao chép tất cả các phần tử, một hàm tạo di chuyển và một phép gán di chuyển sẽ di chuyển tất cả các phần tử. Ví dụ 15Ở đây chúng tôi nhận được 010 và 011. Đó thường chính xác là những gì bạn muốn (và cần thiết cho khả năng tương thích với C), nhưng hãy cân nhắc 16Ở đây, bản sao mặc định cung cấp cho chúng tôi 012 và 013. Điều này dẫn đến thảm họa. khi chúng tôi thoát khỏi 3, hàm hủy cho 015 và 016 được gọi và đối tượng được trỏ bởi 017 và 018 bị xóa hai lầnLàm thế nào để chúng ta tránh điều này? 17Nếu chúng ta cần sao chép hoặc di chuyển, tất nhiên chúng ta có thể xác định các bộ khởi tạo và phép gán thích hợp để cung cấp ngữ nghĩa mong muốn Bây giờ quay lại 081. Đối với 081, ngữ nghĩa sao chép mặc định vẫn ổn, vấn đề là hàm tạo 18Mọi người cung cấp các đối số mặc định để có được sự tiện lợi được sử dụng cho 021 và 022. Sau đó, một số ngạc nhiên bởi việc chuyển đổi 023 thành 024 trong cuộc gọi của 3. Hàm tạo này xác định một chuyển đổi. Theo mặc định, đó là một chuyển đổi ngầm. Để yêu cầu chuyển đổi rõ ràng như vậy, hãy khai báo hàm tạo 573
Chúng ta có thể chuyển con trỏ tới hàm tạo không?Một số người cho rằng bạn không nên sử dụng con trỏ this trong hàm tạo vì đối tượng chưa được hình thành đầy đủ. Tuy nhiên bạn có thể sử dụng điều này trong hàm tạo (trong {body} và thậm chí trong danh sách khởi tạo) nếu bạn cẩn thận .
Hàm tạo có nên có tham số không?Trình tạo cũng có thể nhận tham số , được sử dụng để khởi tạo thuộc tính.
Con trỏ có thể được truyền theo giá trị không?Con trỏ được truyền theo giá trị như mọi thứ khác . Điều đó có nghĩa là nội dung của biến con trỏ (địa chỉ của đối tượng được trỏ tới) được sao chép. Điều đó có nghĩa là nếu bạn thay đổi giá trị của con trỏ trong thân hàm, sự thay đổi đó sẽ không được phản ánh trong con trỏ bên ngoài mà vẫn trỏ đến đối tượng cũ.
Hàm tạo mặc định có thể có tham số không?Hàm tạo mặc định là hàm tạo không có tham số hoặc nếu có tham số thì tất cả các tham số đều có giá trị mặc định . Nếu không có hàm tạo do người dùng định nghĩa nào tồn tại cho một lớp A và cần một hàm tạo, thì trình biên dịch sẽ ngầm khai báo một hàm tạo không tham số mặc định A. MỘT(). |