2016-11-15 21 views
7

Tôi tự hỏi khi nào/nơi diễn ra sự kiện mẫu ngầm trong tình huống sau.Khi nào thì diễn giải mẫu ngầm định xảy ra?

// temp.h 
template <typename T> 
struct A { 
    T value; 
} 
// foo.h 
#include "temp.h" 
void foo(); 
// foo.cpp 
#include "foo.h" 
void foo() { A<int> _foo; } 
// bar.h 
#include "temp.h" 
void bar(); 
// bar.cpp 
#include "bar.h" 
void bar() { A<int> _bar; } 
// main.cpp 
#include "foo.h" 
#include "bar.h" 
int main() { foo(); bar(); return 0; } 

Tôi nghĩ điều đó xảy ra khi foo() được gọi vì đây là lần đầu tiên sử dụng A<int>, vì vậy A<int> được triển khai tại foo.o.
Và, khi bar() được gọi, nó được liên kết với A<int> tại foo.o.

Tôi có đúng không? Hoặc instantiation xảy ra hai lần?

+1

Từ quan điểm của trình biên dịch, sự diễn giải sẽ xảy ra trong khi biên dịch 'foo.cpp' và' bar.cpp' một cách riêng biệt. – songyuanyao

+0

@songyuanyao Oh, sau đó rõ ràng instantiation sẽ rẻ hơn một tiềm ẩn? –

+0

Xin lỗi tôi không chắc chắn về nó; cho thời gian biên dịch nó có thể được, cho thời gian chạy nó phải giống nhau. – songyuanyao

Trả lời

3

Tiêu chuẩn không nói bất cứ điều gì về cách trình biên dịch sẽ khởi tạo mẫu hoàn toàn.

Tôi không chắc chắn về trình biên dịch khác, đây là cách g ++ giao dịch với nó, từ 7.5 Where's the Template?:

Bằng cách nào đó trình biên dịch và mối liên kết phải đảm bảo rằng mỗi trường hợp mẫu xảy ra đúng một lần trong thực thi nếu nó là cần thiết, và không phải ở tất cả các cách khác. Có hai cách tiếp cận cơ bản cho vấn đề này, được gọi là mô hình Borland và mô hình Cfront. mô hình

  • Borland:

Borland C++ giải quyết vấn đề mẫu instantiation bằng cách thêm mã tương đương với khối chung cho mối liên kết của họ; trình biên dịch phát ra các cá thể mẫu trong mỗi đơn vị dịch sử dụng chúng, và trình liên kết thu gọn chúng lại với nhau. Ưu điểm của mô hình này là mối liên kết chỉ phải xem xét các tệp đối tượng; không có sự phức tạp bên ngoài nào phải lo lắng. Điểm bất lợi là thời gian biên dịch được tăng lên vì mã mẫu đang được biên dịch nhiều lần. Mã được viết cho mô hình này có xu hướng bao gồm các định nghĩa của tất cả các mẫu trong tệp tiêu đề, vì chúng phải được xem là được khởi tạo.

  • Cfront mô hình:

Các AT & T C++ dịch, Cfront, giải quyết vấn đề mẫu instantiation bằng cách tạo ra các khái niệm về một kho lưu trữ mẫu, một cách tự động duy trì nơi mẫu trường hợp được được lưu trữ. Một phiên bản hiện đại hơn của kho lưu trữ hoạt động như sau: Khi các tệp đối tượng riêng lẻ được xây dựng, trình biên dịch đặt bất kỳ định nghĩa mẫu và bản ghi nào đã gặp phải trong kho lưu trữ. Tại thời gian liên kết, trình bao bọc liên kết thêm vào trong các đối tượng trong kho lưu trữ và biên dịch bất kỳ trường hợp cần thiết nào mà trước đó không được phát ra. Ưu điểm của mô hình này là tốc độ biên dịch tối ưu hơn và khả năng sử dụng trình liên kết hệ thống; để triển khai mô hình Borland, một nhà cung cấp trình biên dịch cũng cần thay thế trình liên kết.Những bất lợi là phức tạp tăng lên rất nhiều, và do đó có khả năng xảy ra lỗi; đối với một số mã, điều này có thể chỉ là minh bạch, nhưng trong thực tế nó có thể rất khó để xây dựng nhiều chương trình trong một thư mục và một chương trình trong nhiều thư mục. Mã được viết cho mô hình này có xu hướng phân tách các định nghĩa của các mẫu thành viên không nội tuyến thành một tệp riêng biệt, cần được biên dịch riêng biệt.

Đây là cách g ++ xây dựng nó, nhấn mạnh là của tôi:

G ++ xây dựng mô hình Borland vào các mục tiêu mà các mối liên kết hỗ trợ nó, bao gồm cả mục tiêu ELF (như GNU/Linux), Mac OS X và Microsoft Windows. Nếu không, G ++ sẽ không thực hiện mô hình tự động.

Điều đó nói rằng: với g ++, mỗi đơn vị dịch sẽ có cách diễn giải riêng. Nó đã được đề cập một lần nữa trong trang đó:

..., nhưng mỗi đơn vị dịch có chứa các trường hợp của từng mẫu mà nó sử dụng. Các trường hợp trùng lặp sẽ bị loại bỏ bởi trình liên kết, nhưng trong một chương trình lớn, điều này có thể dẫn đến số lượng trùng lặp mã không được chấp nhận trong các tệp đối tượng hoặc các thư viện được chia sẻ.

lựa chọn của bạn để tránh nó (tất nhiên đầu tiên và lựa chọn cuối cùng là rõ ràng):

  1. trường hợp trùng lặp của một mẫu có thể tránh được bằng cách định nghĩa một instantiation rõ ràng trong một tập tin đối tượng, và ngăn trình biên dịch thực hiện các cảnh báo ngầm trong bất kỳ tệp đối tượng nào khác bằng cách sử dụng khai báo khởi tạo rõ ràng, sử dụng cú pháp mẫu extern

  2. Biên dịch mã sử dụng mẫu của bạn với -frepo. Trình biên dịch tạo ra các tệp có phần mở rộng .rpo liệt kê tất cả các mẫu instantiations được sử dụng trong các tệp đối tượng tương ứng có thể được khởi tạo ở đó; trình bao bọc liên kết, collect2, sau đó cập nhật các tệp .rpo để cho trình biên dịch biết vị trí đặt các instantiations đó và xây dựng lại bất kỳ tệp đối tượng bị ảnh hưởng nào. Thời gian liên kết trên không đáng kể sau lần vượt qua đầu tiên, khi trình biên dịch tiếp tục đặt các instantiations trong cùng một tệp.

  3. Biên dịch mã của bạn bằng -fno-implicit-templates để vô hiệu hóa việc tạo ngầm các phiên bản mẫu và nhanh chóng khởi tạo tất cả các phiên bản bạn sử dụng.

2

Theo GNU C++ tài liệu biên dịch (https://gcc.gnu.org/onlinedocs/gcc/Template-Instantiation.html) "mỗi đơn vị dịch có chứa các thể hiện của mỗi người trong số các mẫu nó sử dụng". Vì vậy, trừ khi các đối tượng _foo và _bar không sử dụng của bạn được tối ưu hóa, cả hai tệp đối tượng phải có các bản sao trùng lặp. Sau đó, "các trường hợp trùng lặp sẽ bị loại bỏ bởi trình liên kết", thường có nghĩa là thứ tự liên kết quyết định cá thể nào được sử dụng. Nhưng kết quả cuối cùng sẽ không khác nhau ở một trong hai lệnh.

Các vấn đề liên quan