Đó là vì yêu cầu biên dịch riêng biệt và bởi vì các mẫu là đa hình kiểu khởi tạo.
Hãy đến gần hơn một chút để giải thích. Nói rằng tôi đã có các tệp sau:
- foo.h
- tuyên bố giao diện của
class MyClass<T>
- foo.cpp
- định nghĩa việc thực hiện
class MyClass<T>
- bar.cpp
Phương tiện biên soạn riêng tôi sẽ có thể biên dịch Foo.cpp một cách độc lập từ bar.cpp . Trình biên dịch thực hiện tất cả các công việc khó khăn để phân tích, tối ưu hóa và tạo mã trên mỗi đơn vị biên dịch hoàn toàn độc lập; chúng ta không cần phải phân tích toàn bộ chương trình. Chỉ có trình liên kết cần xử lý toàn bộ chương trình cùng một lúc và công việc của trình liên kết dễ dàng hơn nhiều.
bar.cpp thậm chí không cần phải tồn tại khi tôi biên dịch Foo.cpp , nhưng tôi vẫn sẽ có thể liên kết các foo.o tôi đã có cùng với bar.o tôi đã chỉ mới sản xuất, mà không cần phải biên dịch lại foo .cpp . foo.cpp thậm chí có thể được biên dịch thành một thư viện động, được phân phối ở một nơi khác mà không có foo.cpp và được liên kết với mã họ viết nhiều năm sau khi tôi viết foo.cpp .
"Đa hình theo kiểu tức thời" có nghĩa là mẫu MyClass<T>
không thực sự là một lớp chung có thể được biên dịch thành mã có thể hoạt động với bất kỳ giá trị nào T
. Điều đó sẽ thêm chi phí như boxing, cần phải vượt qua con trỏ hàm để allocators và nhà thầu vv Mục đích của C ++ mẫu là để tránh phải viết gần như giống hệt class MyClass_int
, class MyClass_float
, vv, nhưng vẫn có thể kết thúc với mã biên dịch đó là chủ yếu như thể chúng tôi đã viết riêng từng phiên bản. Vì vậy, một mẫu có nghĩa đen là một mẫu; một mẫu lớp không phải là một lớp, nó là một công thức để tạo một lớp mới cho mỗi T
chúng ta gặp phải. Một mẫu không thể được biên dịch thành mã, chỉ có thể biên dịch kết quả của mẫu.
Vì vậy, khi foo.cpp được biên dịch, trình biên dịch không thể thấy bar.cpp để biết điều đó MyClass<int>
là cần thiết. Nó có thể nhìn thấy mẫu MyClass<T>
, nhưng nó không thể phát ra mã cho điều đó (đó là một mẫu chứ không phải một lớp). Và khi bar.cpp được biên dịch, trình biên dịch có thể thấy rằng nó cần tạo một MyClass<int>
, nhưng nó không thể nhìn thấy mẫu MyClass<T>
(chỉ giao diện của nó trong foo.h ) vì vậy nó không thể tạo ra nó.
Nếu foo.cpp tự sử dụng MyClass<int>
, thì mã đó sẽ được tạo trong khi biên dịch foo.cpp , vì vậy khi bar.o được liên kết với foo.o chúng có thể được nối và sẽ hoạt động. Chúng ta có thể sử dụng thực tế đó để cho phép một tập hợp hữu hạn các mẫu tức thời được triển khai trong tệp .cpp bằng cách viết một mẫu duy nhất. Nhưng không có cách nào để bar.cpp sử dụng mẫu làm mẫu và khởi tạo nó trên bất kỳ loại nào nó thích; nó chỉ có thể sử dụng các phiên bản có sẵn của lớp templated mà tác giả của foo.cpp nghĩ để cung cấp.
Bạn có thể nghĩ rằng khi biên dịch một mẫu, trình biên dịch sẽ "tạo ra tất cả các phiên bản", với các phiên bản không bao giờ được sử dụng sẽ được lọc ra trong quá trình liên kết. Ngoài chi phí rất lớn và những khó khăn cực kỳ mà cách tiếp cận sẽ gặp phải vì các tính năng "sửa đổi kiểu" như con trỏ và mảng cho phép ngay cả các loại tích hợp cũng tạo ra vô số loại, điều xảy ra khi tôi mở rộng chương trình của mình bằng cách thêm:
- baz.cpp
- tuyên bố và thực hiện
class BazPrivate
, và sử dụngMyClass<BazPrivate>
Không có cách nào có thể làm việc này trừ khi chúng ta
- Phải biên dịch lại foo.cpp mỗi khi chúng tôi thay đổi bất kỳ tệp nào khác trong chương trình , trong trường hợp nó thêm một phần khởi tạo tiểu thuyết mới của
MyClass<T>
- Yêu cầu baz.cpp chứa (có thể thông qua tiêu đề bao gồm) mẫu đầy đủ của
MyClass<T>
, để trình biên dịch có thể tạo MyClass<BazPrivate>
trong quá trình biên dịch baz.cpp .
Không ai thích (1), bởi vì các hệ thống biên dịch phân tích toàn bộ chương trình mất mãi mãi để biên dịch và vì nó không thể phân phối các thư viện đã biên dịch mà không có mã nguồn. Vì vậy, chúng tôi có (2) thay thế.