sử dụng mẫu extern (C ++ 11)


116

Hình 1: các mẫu hàm

TemplHead.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){
   //...
}    
//explicit instantation
template void f<T>();

Main.cpp

#include "TemplHeader.h"
extern template void f<T>(); //is this correct?
int main() {
    f<char>();
    return 0;
}

Đây có phải là cách sử dụng đúng extern templatehay tôi chỉ sử dụng từ khóa này cho các mẫu lớp như trong Hình 2?

Hình 2: các mẫu lớp

TemplHead.h

template<typename T>
class foo {
    T f();
};

TemplCpp.cpp

template<typename T>
void foo<T>::f() {
    //...
}
//explicit instantation
template class foo<int>;

Main.cpp

#include "TemplHeader.h"
extern template class foo<int>();
int main() {
    foo<int> test;
    return 0;
}

Tôi biết sẽ tốt khi đặt tất cả những thứ này vào một tệp tiêu đề, nhưng nếu chúng tôi khởi tạo các mẫu có cùng tham số trong nhiều tệp, thì chúng tôi có nhiều định nghĩa giống nhau và trình biên dịch sẽ xóa tất cả (trừ một) để tránh lỗi. Làm thế nào để tôi sử dụng extern template? Chúng ta có thể sử dụng nó chỉ cho các lớp, hoặc chúng ta cũng có thể sử dụng nó cho các chức năng?

Ngoài ra, Hình 1 và Hình 2 có thể được mở rộng thành một giải pháp trong đó các mẫu nằm trong một tệp tiêu đề duy nhất. Trong trường hợp đó, chúng ta cần sử dụng extern templatetừ khóa để tránh nhiều lần xuất hiện giống nhau. Đây có phải chỉ dành cho các lớp hoặc chức năng quá?


3
Đây hoàn toàn không phải là cách sử dụng chính xác của các mẫu bên ngoài ... điều này thậm chí còn không được biên dịch
Dani

Bạn có thể dành chút thời gian để diễn đạt câu hỏi (một) rõ ràng hơn không? Bạn đang đăng mã để làm gì? Tôi không thấy một câu hỏi liên quan đến điều đó. Ngoài ra, extern template class foo<int>();có vẻ như một sai lầm.
sehe

@Dani> nó chỉ biên dịch tốt trên studio hình ảnh của tôi 2010 ngoại trừ thông báo cảnh báo: Cảnh báo 1 cảnh báo C4231: tiện ích mở rộng không chuẩn được sử dụng: 'extern' trước khi khởi tạo mẫu rõ ràng
codekiddy

2
@sehe câu hỏi rất đơn giản: làm thế nào và khi nào nên sử dụng từ khóa mẫu extern? (mẫu extern là C ++ 0x btw tương lai mới) bạn đã nói "Ngoài ra, lớp mẫu extern foo <int> (); có vẻ như là một lỗi." không, không phải vậy, tôi có cuốn sách C ++ mới và đó là ví dụ từ cuốn sách của tôi.
codekiddy

1
@codekiddy: thì studio hình ảnh thực sự rất ngu ngốc .. trong phần thứ hai, nguyên mẫu không phù hợp với việc triển khai và ngay cả khi tôi sửa nó có nội dung 'id không đủ tiêu chuẩn' ở gần ()dòng bên ngoài. cả sách và studio trực quan của bạn đều sai, hãy thử sử dụng trình biên dịch tuân thủ tiêu chuẩn hơn như g ++ hoặc clang và bạn sẽ thấy vấn đề.
Dani

Câu trả lời:


181

Bạn chỉ nên sử dụng extern templateđể buộc trình biên dịch không khởi tạo một mẫu khi bạn biết rằng nó sẽ được khởi tạo ở một nơi khác. Nó được sử dụng để giảm thời gian biên dịch và kích thước tệp đối tượng.

Ví dụ:

// header.h

template<typename T>
void ReallyBigFunction()
{
    // Body
}

// source1.cpp

#include "header.h"
void something1()
{
    ReallyBigFunction<int>();
}

// source2.cpp

#include "header.h"
void something2()
{
    ReallyBigFunction<int>();
}

Điều này sẽ dẫn đến các tệp đối tượng sau:

source1.o
    void something1()
    void ReallyBigFunction<int>()    // Compiled first time

source2.o
    void something2()
    void ReallyBigFunction<int>()    // Compiled second time

Nếu cả hai tệp được liên kết với nhau, một tệp void ReallyBigFunction<int>()sẽ bị loại bỏ, dẫn đến lãng phí thời gian biên dịch và kích thước tệp đối tượng.

Để không lãng phí thời gian biên dịch và kích thước tệp đối tượng, có một externtừ khóa làm cho trình biên dịch không biên dịch một hàm mẫu. Bạn nên sử dụng cái này khi và chỉ khi bạn biết nó được sử dụng trong cùng một nhị phân ở một nơi khác.

Đổi source2.cppthành:

// source2.cpp

#include "header.h"
extern template void ReallyBigFunction<int>();
void something2()
{
    ReallyBigFunction<int>();
}

Sẽ dẫn đến các tệp đối tượng sau:

source1.o
    void something1()
    void ReallyBigFunction<int>() // compiled just one time

source2.o
    void something2()
    // No ReallyBigFunction<int> here because of the extern

Khi cả hai sẽ được liên kết với nhau, tệp đối tượng thứ hai sẽ chỉ sử dụng ký hiệu từ tệp đối tượng đầu tiên. Không cần loại bỏ và không lãng phí thời gian biên dịch và kích thước tệp đối tượng.

Điều này chỉ nên được sử dụng trong một dự án, như trong những lúc bạn sử dụng một mẫu như vector<int>nhiều lần, bạn nên sử dụng externtrong tất cả trừ một tệp nguồn.

Điều này cũng áp dụng cho các lớp và chức năng như một, và thậm chí các hàm thành viên mẫu.


2
@codekiddy: Tôi không biết Visual Studio nghĩa là gì. Bạn thực sự nên sử dụng một trình biên dịch tuân thủ hơn nếu bạn muốn hầu hết mã c ++ 11 hoạt động tốt.
Dani

4
@Dani: lời giải thích tốt nhất về các mẫu extern tôi đã đọc cho đến nay!
Pietro

90
"Nếu bạn biết nó được sử dụng trong cùng một nhị phân ở một nơi khác.". Điều đó là không đủ và cũng không bắt buộc. Mã của bạn là "không định hình, không cần chẩn đoán". Bạn không được phép dựa vào một khởi tạo ngầm định của một TU khác (trình biên dịch được phép tối ưu hóa nó đi, giống như một hàm nội tuyến). Một khởi tạo rõ ràng phải được cung cấp trong một TU khác.
Julian Schaub - litb

32
Tôi muốn chỉ ra rằng câu trả lời này có lẽ sai và tôi đã bị nó cắn. May mắn thay, bình luận của Julian đã có một số phiếu bầu lên và tôi đã chú ý nhiều hơn vào lần này. Tôi chỉ có thể giả định rằng đại đa số cử tri về câu hỏi này đã không thực sự triển khai các loại mẫu này trong nhiều đơn vị biên dịch (như tôi đã làm ngày hôm nay) ... Ít nhất là đối với tiếng kêu, cách chắc chắn duy nhất là đưa các định nghĩa mẫu này vào mở đầu của bạn! Được cảnh báo!
Steven Lu

6
@ JohannesSchaub-litb, bạn có thể giải thích thêm một chút hoặc có thể cung cấp một câu trả lời tốt hơn? Tôi không chắc chắn nếu tôi hoàn toàn hiểu sự phản đối của bạn.
andreee

48

Wikipedia có mô tả tốt nhất

Trong C ++ 03, trình biên dịch phải khởi tạo một mẫu bất cứ khi nào một mẫu được chỉ định đầy đủ gặp phải trong một đơn vị dịch thuật. Nếu mẫu được khởi tạo với cùng loại trong nhiều đơn vị dịch thuật, điều này có thể làm tăng đáng kể thời gian biên dịch. Không có cách nào để ngăn chặn điều này trong C ++ 03, vì vậy C ++ 11 đã giới thiệu các khai báo mẫu bên ngoài, tương tự như khai báo dữ liệu bên ngoài.

C ++ 03 có cú pháp này để bắt buộc trình biên dịch khởi tạo một mẫu:

  template class std::vector<MyClass>;

C ++ 11 hiện cung cấp cú pháp này:

  extern template class std::vector<MyClass>;

thông báo cho trình biên dịch không khởi tạo mẫu trong đơn vị dịch này.

Cảnh báo: nonstandard extension used...

Microsoft VC ++ đã từng có phiên bản không chuẩn của tính năng này trong một số năm (trong C ++ 03). Trình biên dịch cảnh báo về điều đó để ngăn chặn các vấn đề về tính di động với mã cần biên dịch trên các trình biên dịch khác nhau.

Nhìn vào mẫu trong trang được liên kết để thấy rằng nó hoạt động gần giống như vậy. Bạn có thể mong đợi tin nhắn sẽ biến mất với các phiên bản MSVC trong tương lai, ngoại trừ khi sử dụng các phần mở rộng trình biên dịch không chuẩn khác cùng một lúc.


tnx cho sehe trả lời của bạn, vậy điều này có nghĩa là "mẫu extern" này hoạt động hoàn toàn cho VS 2010 và chúng ta có thể bỏ qua cảnh báo không? (sử dụng pragma để bỏ qua thông báo chẳng hạn) và là bờ mà mẫu không được khởi tạo nhiều hơn sau đó đúng giờ trong VSC ++. trình biên dịch. cảm ơn.
codekiddy

4
"... thông báo cho trình biên dịch không khởi tạo mẫu trong đơn vị dịch thuật này ." Tôi không nghĩ điều này là đúng. Bất kỳ phương thức nào được định nghĩa trong định nghĩa lớp đều được tính là nội tuyến, do đó, nếu việc triển khai STL sử dụng các phương thức nội tuyến cho std::vector(khá chắc chắn tất cả chúng đều làm), externsẽ không có hiệu lực.
Andreas Haferburg

Đúng, câu trả lời này là sai lệch. Tài liệu MSFT: "Từ khóa bên ngoài trong chuyên môn chỉ áp dụng cho các hàm thành viên được xác định bên ngoài phần thân của lớp. Các hàm được định nghĩa bên trong khai báo lớp được coi là các hàm nội tuyến và luôn được khởi tạo." Tất cả các lớp STL trong VS (kiểm tra lần cuối là 2017) chỉ có các phương thức nội tuyến không may.
0kcats

Điều đó áp dụng cho tất cả các khai báo nội tuyến bất kể chúng phát sinh ở đâu, luôn luôn @ 0kcats
sehe

@sehe Tham chiếu đến Wiki với ví dụ về std :: vector và tham chiếu đến MSVC trong cùng một câu trả lời khiến người ta tin rằng có thể có một số lợi ích khi sử dụng extern std :: vector trong MSVC, trong khi cho đến nay vẫn chưa có. Không chắc chắn nếu đây là yêu cầu của tiêu chuẩn, có thể các trình biên dịch khác có cùng một vấn đề.
0kcats

7

extern template chỉ cần thiết nếu khai báo mẫu hoàn tất

Điều này đã được gợi ý trong các câu trả lời khác, nhưng tôi không nghĩ rằng đã nhấn mạnh đủ vào nó.

Điều này có nghĩa là trong các ví dụ về OP, extern templatekhông có tác dụng vì các định nghĩa mẫu trên các tiêu đề không đầy đủ:

  • void f();: chỉ cần khai báo, không có cơ thể
  • class foo: khai báo phương thức f()nhưng không có định nghĩa

Vì vậy, tôi khuyên bạn chỉ nên xóa extern templateđịnh nghĩa trong trường hợp cụ thể đó: bạn chỉ cần thêm chúng nếu các lớp được xác định hoàn toàn.

Ví dụ:

TemplHead.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){}

// Explicit instantiation for char.
template void f<char>();

Main.cpp

#include "TemplHeader.h"

// Commented out from OP code, has no effect.
// extern template void f<T>(); //is this correct?

int main() {
    f<char>();
    return 0;
}

biên dịch và xem các biểu tượng với nm:

g++ -std=c++11 -Wall -Wextra -pedantic -c -o TemplCpp.o TemplCpp.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -c -o Main.o Main.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -o Main.out Main.o TemplCpp.o
echo TemplCpp.o
nm -C TemplCpp.o | grep f
echo Main.o
nm -C Main.o | grep f

đầu ra:

TemplCpp.o
0000000000000000 W void f<char>()
Main.o
                 U void f<char>()

và sau đó từ man nmchúng tôi thấy điều đó Ucó nghĩa là không xác định, vì vậy định nghĩa chỉ duy trì TemplCppnhư mong muốn.

Tất cả điều này rút lại cho sự đánh đổi của các tuyên bố tiêu đề hoàn chỉnh:

  • mặt tích cực:
    • cho phép mã bên ngoài sử dụng mẫu của chúng tôi với các loại mới
    • chúng ta có tùy chọn không thêm các cảnh báo rõ ràng nếu chúng ta ổn với sự phình to của đối tượng
  • nhược điểm:
    • Khi phát triển lớp đó, các thay đổi triển khai tiêu đề sẽ dẫn đến các hệ thống xây dựng thông minh để xây dựng lại tất cả các bao gồm, có thể có nhiều tệp
    • nếu chúng ta muốn tránh sự phình to của tệp đối tượng, chúng ta không chỉ cần thực hiện các cảnh báo rõ ràng (giống như với các khai báo tiêu đề không đầy đủ) mà còn thêm extern templatevào mỗi bao gồm, điều mà các lập trình viên có thể sẽ quên làm

Các ví dụ khác về những thứ này được hiển thị tại: Khởi tạo mẫu rõ ràng - khi nào nó được sử dụng?

Vì thời gian biên dịch rất quan trọng trong các dự án lớn, tôi rất khuyến nghị khai báo mẫu không đầy đủ, trừ khi các bên ngoài hoàn toàn cần sử dụng lại mã của bạn với các lớp tùy chỉnh phức tạp của riêng họ.

Và trong trường hợp đó, trước tiên tôi sẽ cố gắng sử dụng đa hình để tránh vấn đề thời gian xây dựng và chỉ sử dụng các mẫu nếu có thể đạt được hiệu suất đáng chú ý.

Đã thử nghiệm trong Ubuntu 18.04.


4

Vấn đề đã biết với các mẫu là sự phình to mã, là kết quả của việc tạo định nghĩa lớp trong mỗi và mọi mô-đun gọi ra chuyên môn hóa mẫu lớp. Để ngăn chặn điều này, bắt đầu với C ++ 0x, người ta có thể sử dụng từ khóa extern trước chuyên ngành mẫu lớp

#include <MyClass>
extern template class CMyClass<int>;

Việc khởi tạo rõ ràng của lớp mẫu chỉ nên xảy ra trong một đơn vị dịch, tốt nhất là đơn vị có định nghĩa mẫu (MyClass.cpp)

template class CMyClass<int>;
template class CMyClass<float>;

0

Nếu bạn đã sử dụng extern cho các hàm trước đó, chính xác triết lý tương tự được theo sau cho các mẫu. nếu không, đi mặc dù extern cho các chức năng đơn giản có thể giúp đỡ. Ngoài ra, bạn có thể muốn đặt extern (s) trong tệp tiêu đề và bao gồm tiêu đề khi bạn cần.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.