nhiều định nghĩa về chuyên môn hóa mẫu khi sử dụng các đối tượng khác nhau


95

Khi tôi sử dụng một mẫu chuyên biệt trong các tệp đối tượng khác nhau, tôi gặp lỗi "nhiều định nghĩa" khi liên kết. Giải pháp duy nhất mà tôi tìm thấy liên quan đến việc sử dụng hàm "nội tuyến", nhưng có vẻ như đây chỉ là một số cách giải quyết. Làm cách nào để giải quyết vấn đề đó mà không sử dụng từ khoá "nội tuyến"? Nếu điều đó là không thể, tại sao?

Đây là mã ví dụ:

paulo@aeris:~/teste/cpp/redef$ cat hello.h 
#ifndef TEMPLATE_H
#define TEMPLATE_H

#include <iostream>

template <class T>
class Hello
{
public:
    void print_hello(T var);
};

template <class T>
void Hello<T>::print_hello(T var)
{
    std::cout << "Hello generic function " << var << "\n";
}

template <> //inline
void Hello<int>::print_hello(int var)
{
    std::cout << "Hello specialized function " << var << "\n";
}

#endif

paulo@aeris:~/teste/cpp/redef$ cat other.h 
#include <iostream>

void other_func();

paulo@aeris:~/teste/cpp/redef$ cat other.c 
#include "other.h"

#include "hello.h"

void other_func()
{
    Hello<char> hc;
    Hello<int> hi;

    hc.print_hello('a');
    hi.print_hello(1);
}

paulo@aeris:~/teste/cpp/redef$ cat main.c 
#include "hello.h"

#include "other.h"

int main()
{
    Hello<char> hc;
    Hello<int> hi;

    hc.print_hello('a');
    hi.print_hello(1);

    other_func();

    return 0;
}

paulo@aeris:~/teste/cpp/redef$ cat Makefile
all:
    g++ -c other.c -o other.o -Wall -Wextra
    g++ main.c other.o -o main -Wall -Wextra

Cuối cùng:

paulo@aeris:~/teste/cpp/redef$ make
g++ -c other.c -o other.o -Wall -Wextra
g++ main.c other.o -o main -Wall -Wextra
other.o: In function `Hello<int>::print_hello(int)':
other.c:(.text+0x0): multiple definition of `Hello<int>::print_hello(int)'
/tmp/cc0dZS9l.o:main.c:(.text+0x0): first defined here
collect2: ld returned 1 exit status
make: ** [all] Erro 1

Nếu tôi bỏ ghi chú "nội tuyến" bên trong hello.h, mã sẽ biên dịch và chạy, nhưng điều đó có vẻ giống như một loại "giải pháp" đối với tôi: điều gì sẽ xảy ra nếu hàm chuyên dụng lớn và được sử dụng nhiều lần? Tôi sẽ nhận được một nhị phân lớn? Có cách nào khác để thực hiện điều này không? Nếu có, làm thế nào? Nếu không, tại sao?

Tôi đã cố gắng tìm kiếm câu trả lời, nhưng tất cả những gì tôi nhận được là "sử dụng nội tuyến" mà không cần giải thích gì thêm.

Cảm ơn


6
đặt thực hiện chuyên môn thực tế vào cpp thay vì sau đó tập tin tiêu đề
Anycorn

Câu trả lời:


129

Theo trực giác, khi bạn hoàn toàn chuyên môn hóa một thứ gì đó, nó sẽ không phụ thuộc vào tham số mẫu nữa - vì vậy trừ khi bạn thực hiện nội tuyến chuyên môn hóa đó, bạn cần đặt nó vào tệp .cpp thay vì .h, nếu không bạn sẽ vi phạm một quy tắc định nghĩa như David nói. Lưu ý rằng khi bạn chuyên môn hóa một phần các mẫu, các chuyên môn hóa một phần vẫn phụ thuộc vào một hoặc nhiều tham số mẫu, vì vậy chúng vẫn đi trong tệp .h.


Hmmm Tôi vẫn hơi bối rối về cách nó phá vỡ ODR. Bởi vì bạn chỉ xác định mẫu hoàn toàn chuyên biệt một lần. Bạn có thể tạo đối tượng nhiều lần trong các tệp đối tượng khác nhau (tức là trong trường hợp này, nó được khởi tạo trong other.c và main.c) nhưng bản thân đối tượng ban đầu chỉ được xác định trong một tệp - trong trường hợp này hello.h.
Justin Liang

3
@JustinLiang: Tiêu đề được bao gồm trong hai tệp .c riêng biệt - có tác dụng tương tự như khi bạn viết nội dung của nó (bao gồm toàn bộ chuyên môn) trực tiếp vào các tệp mà nó được đưa vào ở những nơi liên quan. Quy tắc Một Định nghĩa (xem en.wikipedia.org/wiki/One_Definition_Rule ) nói (trong số những thứ khác): "Trong toàn bộ chương trình, một đối tượng hoặc hàm không nội tuyến không được có nhiều hơn một định nghĩa". Trong trường hợp này, sự chuyên biệt hóa đầy đủ của mẫu hàm về bản chất cũng giống như một hàm bình thường, vì vậy trừ khi nó nội dòng, nó không thể có nhiều hơn một định nghĩa.
Stuart Golodetz

Rất tiếc, tôi nhận thấy rằng khi chúng ta không có chuyên môn tạo mẫu, lỗi này sẽ không xuất hiện. Giả sử chúng ta có hai hàm khác nhau đã được định nghĩa trong tệp tiêu đề, bên ngoài lớp, chúng sẽ vẫn hoạt động mà không có nội tuyến? Ví dụ: pastebin.com/raw.php?i=bRaiNC7M . Tôi đã lấy lớp đó và đưa nó vào hai tệp. Điều này sẽ không có "tác dụng giống như khi bạn viết nội dung" trực tiếp vào hai tệp và do đó sẽ xảy ra lỗi nhiều định nghĩa?
Justin Liang

@Justin Liang, mã tiêu đề dựa trên lớp của bạn sẽ vẫn vi phạm ODR nếu được bao gồm trong nhiều tệp, trừ khi định nghĩa hàm nằm trong phần nội dung của lớp.
haripkannan

Vì vậy, nếu định nghĩa thành viên tĩnh của tôi đứng trước template <typename T>thì nó có thể đi vào tiêu đề, và nếu nó template<>thì có thể không?
Violet Giraffe,

49

Từ khóa inlinehướng đến việc nói với trình biên dịch rằng biểu tượng sẽ hiện diện trong nhiều tệp đối tượng mà không vi phạm Quy tắc Một Định nghĩa hơn là về nội tuyến thực tế, mà trình biên dịch có thể quyết định làm hoặc không làm.

Vấn đề bạn đang thấy là nếu không có nội tuyến, hàm sẽ được biên dịch trong tất cả các đơn vị dịch bao gồm tiêu đề, vi phạm ODR. Thêm vào inlineđó là con đường đúng đắn để đi. Nếu không, bạn có thể chuyển tiếp khai báo chuyên ngành và cung cấp nó trong một đơn vị dịch duy nhất, như cách bạn làm với bất kỳ chức năng nào khác.


22

Bạn đã khởi tạo một cách rõ ràng một mẫu trong tiêu đề ( void Hello<T>::print_hello(T var)) của mình. Điều này sẽ tạo ra nhiều định nghĩa. Bạn có thể giải quyết nó theo hai cách:

1) Tạo nội tuyến bản khởi tạo của bạn.

2) Khai báo sự khởi tạo trong một tiêu đề và sau đó triển khai nó trong một cpp.


Trên thực tế có một cách thứ 3 mà là để đưa những người trong một không gian tên không có tên ... đó là tương tự như có tĩnh trong C.
Alexis Wilke

4
Điều đó không hợp lệ ở đây. Một chuyên ngành mẫu cần phải ở trong cùng một không gian tên với mẫu gốc.
Edward Strange

0

Dưới đây là một số phần của tiêu chuẩn C ++ 11 liên quan đến vấn đề này:

Sự chuyên môn hóa rõ ràng của một mẫu hàm chỉ là nội tuyến nếu nó được khai báo với bộ chỉ định nội tuyến hoặc được định nghĩa là đã xóa và độc lập với việc mẫu hàm của nó có nội tuyến hay không. [ Thí dụ:

mẫu void f (T) {/ * ... /} mẫu nội dòng T g (T) {/ ... * /}

template <> inline void f <> (int) {/ * ... /} // OK: inline template <> int g <> (int) {/ ... * /} // OK: not inline - end thí dụ ]

Vì vậy, nếu bạn thực hiện một số chuyên môn rõ ràng (hay còn gọi là đầy đủ) của các mẫu trong *.htệp, thì bạn vẫn cần inlinegiúp bạn loại bỏ vi phạm ODR .

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.