2013-05-06 26 views
7

Tôi có các chức năng templated trong dự án C++ 11 Xcode của tôi và một số trong chúng có chuyên môn. Tuy nhiên, tôi đã phát hiện ra rằng các chuyên môn chỉ được gọi trong các bản dựng gỡ lỗi; nếu tôi xây dựng trong bản phát hành, họ sẽ bị bỏ qua.Tại sao chức năng mẫu chuyên biệt của tôi chỉ được gọi trong các bản dựng gỡ lỗi?

tôi đã tạo thành công một ví dụ rất đơn giản:

special.h

#include <cstdio> 

struct special 
{ 
    template<typename T> 
    void call(const T&) { puts("not so special"); } 
}; 

special.cpp

#include "special.h" 
#include <string> 

template<> 
void special::call(const std::string&) { puts("very special"); } 

main.cpp

#include "special.h" 
#include <string> 

int main() 
{ 
    std::string str = "hello world"; 
    special s; 
    s.call(123); 
    s.call(str); 
} 

You can download the project (cho đến một nơi nào đó vào mùa hè này ít nhất là 2013) để tái tạo sự cố nếu bạn không muốn tự tạo ra sự cố. Trước tiên hãy chạy dự án với cấu hình gỡ lỗi, sau đó chạy lại nó trong bản phát hành. Kết quả mà tôi mong đợi là:

không quá đặc biệt
rất đặc biệt

Và đây thực sự là những gì tôi nhận được với cấu hình Debug xây dựng. Tuy nhiên, với phát hành, tôi có được điều này:

không quá đặc biệt
không quá đặc biệt

Có nghĩa là việc thực hiện chuyên môn của special::call trong special.cpp đã bị bỏ qua.

Tại sao kết quả không phù hợp? Tôi nên làm gì để đảm bảo rằng chức năng chuyên biệt được gọi trong bản phát hành bản phát hành?

+0

Đặt nó vào .h thay vì .cpp? – Pubby

+0

special.cpp được liên kết trong cả hai trường hợp? – ForEveR

+0

Có, special.cpp được biên dịch và liên kết trong cả hai trường hợp. Nếu tôi đặt chuyên môn trong tệp tiêu đề, nó hoạt động nếu tệp được bao gồm trong một tệp .cpp, nhưng nếu không tôi sẽ gặp lỗi biểu tượng trùng lặp. – zneak

Trả lời

11

Chương trình của bạn có UB. Một chuyên môn rõ ràng hoặc ít nhất tuyên bố của nó phải được nhìn thấy trước khi được sử dụng. [Temp.expl.spec] §6:

Nếu một mẫu, một mẫu thành viên hoặc thành viên của một lớp mẫu là rõ ràng chuyên ngành sau đó chuyên môn hóa đó sẽ được công bố trước khi sử dụng đầu tiên của chuyên môn hóa đó mà có gây ra một sự phát hiện ngầm định , trong mọi đơn vị dịch trong việc sử dụng đó xảy ra; không cần chẩn đoán.

Thêm tuyên bố này để :

template<> 
void special::call(const std::string&); 

Ngoài ra, bạn có thể đặt specialistation mình thành tiêu đề. Tuy nhiên, khi chuyên môn hóa không còn là mẫu, nó tuân theo các quy tắc chức năng bình thường và phải được đánh dấu inline nếu được đặt trong tiêu đề.

Ngoài ra, hãy cẩn thận rằng các chuyên môn về mẫu chức năng có hành vi khá cụ thể và thường tốt hơn là sử dụng quá tải hơn so với các chuyên môn. Xem Herb Sutter's article để biết chi tiết.

8

Bạn đã vi phạm quy tắc định nghĩa một (ODR). Vậy điều gì xảy ra chính xác? Trong main.cpp, không có chuyên môn nào được biết cho special::call<string>. Do đó trình biên dịch tạo ra một sự khởi tạo của mẫu vào đơn vị dịch thuật (TU) mà kết quả đầu ra "không quá đặc biệt". Trong special.cpp có một chuyên môn đầy đủ được khai báo và xác định, do đó trình biên dịch đặt định nghĩa đó vào đơn vị dịch thuật khác. Vì vậy, bạn có hai định nghĩa khác nhau về cùng một chức năng trong hai đơn vị dịch thuật khác nhau, đó là sự vi phạm ODR có nghĩa là nó là hành vi không xác định.

Về lý thuyết, kết quả có thể là bất kỳ thứ gì. Lỗi trình biên dịch, sự cố, đơn đặt hàng trực tuyến im lặng cho pizza, mọi thứ. Ngay cả hành vi khác nhau trong gỡ lỗi và phát hành các biên dịch.

Trong thực tế, tôi đoán điều sau đây xảy ra: Khi liên kết xây dựng gỡ lỗi, Trình liên kết sẽ nhìn thấy cùng một biểu tượng được xác định hai lần trong hai TU, chỉ được phép cho mẫu và hàm nội tuyến. Bởi vì ODR nó có thể giả định rằng cả hai định nghĩa là tương đương và chọn một từ special.cpp, vì vậy bạn nhận được do trùng hợp ngẫu nhiên hành vi mà bạn mong đợi.
Trong khi xây dựng bản phát hành, trình biên dịch inline cuộc gọi đến special::call<string> trong quá trình biên soạn main.cpp, vì vậy bạn sẽ có được hành vi duy nhất được thấy trong TU đó: "không quá đặc biệt".

Vậy làm thế nào bạn có thể sửa lỗi này?
Để chỉ có một định nghĩa cho chuyên môn đó, bạn phải xác định trong một TU như bạn đã làm, nhưng bạn phải tuyên bố rằng có một chuyên môn đầy đủ trong bất kỳ TU nào khác, có nghĩa là tuyên bố rằng chuyên môn tồn tại trong tiêu đề :

// in special.h 
template<> 
void special::call(const std::string&); 

Hoặc, được xem thường xuyên hơn, xác định nó trong tiêu đề, do đó, nó nhìn thấy trong mỗi TU. Vì các mẫu chức năng chuyên biệt hoàn toàn là các chức năng bình thường, bạn sẽ phải xác định nội tuyến:

// in special.h 
template<> 
inline void special::call(const std::string&) 
{ 
    puts("very special"); 
} 
+0

+1 để được giải thích đầy đủ về những gì đang diễn ra và không chỉ là sửa chữa được yêu cầu. Khi lựa chọn hàm từ 'specials.cpp', tôi sẽ phỏng đoán rằng đó là do thứ tự các tập tin' .o' được truyền tới trình liên kết; cụ thể là tôi nghi ngờ rằng 'specials.o' được chuyển trước' main.o'. –

+0

Tôi đoán đó là phần * rất triển khai cụ thể. Nó sẽ phụ thuộc vào thứ tự mà trong đó trình liên kết * xử lý các tệp đối tượng, điều này lần lượt phụ thuộc vào thứ tự các tệp đối tượng được truyền vào nó thông qua dòng lệnh. Và nó có thể phụ thuộc vào cách trình liên kết lưu trữ và tra cứu các biểu tượng mà nó tìm thấy trong hai TU. Đó có thể là bất cứ điều gì, nhưng tôi đoán quá rõ ràng nhất là tệp đối tượng đầu tiên được truyền là lần đầu tiên được xử lý và lần đầu tiên biểu tượng được tìm thấy là biểu tượng duy nhất được lưu trữ và gọi. –

+0

Ồ, bạn nói đúng, có vẻ như rất cụ thể, nhưng vì dường như hành vi là * ổn định *, có lẽ là một lời giải thích. –

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