ความหมายหลายอย่างของความเชี่ยวชาญเทมเพลตเมื่อใช้วัตถุที่แตกต่างกัน


100

เมื่อฉันใช้เทมเพลตพิเศษในไฟล์ออบเจ็กต์อื่นฉันได้รับข้อผิดพลาด "คำจำกัดความหลายรายการ" เมื่อทำการเชื่อมโยง วิธีแก้ปัญหาเดียวที่ฉันพบคือการใช้ฟังก์ชัน "อินไลน์" แต่ดูเหมือนวิธีแก้ปัญหาบางอย่าง ฉันจะแก้ปัญหาโดยไม่ใช้คีย์เวิร์ด "อินไลน์" ได้อย่างไร ถ้าเป็นไปไม่ได้ทำไม?

นี่คือรหัสตัวอย่าง:

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

สุดท้าย:

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

ถ้าฉันยกเลิกการใส่เครื่องหมาย "อินไลน์" ไว้ใน hello.h โค้ดจะคอมไพล์และรัน แต่ดูเหมือนจะเป็น "วิธีแก้ปัญหา" บางอย่างสำหรับฉัน: จะเกิดอะไรขึ้นถ้าฟังก์ชันพิเศษมีขนาดใหญ่และใช้หลายครั้ง ฉันจะได้รับไบนารีขนาดใหญ่หรือไม่? มีวิธีอื่นในการทำเช่นนี้หรือไม่? ถ้าใช่อย่างไร ถ้าไม่เพราะเหตุใด

ฉันพยายามมองหาคำตอบ แต่สิ่งที่ได้คือ "ใช้อินไลน์" โดยไม่มีคำอธิบายเพิ่มเติม

ขอบคุณ


7
ใส่การใช้งานเฉพาะทางจริงลงใน. cpp แทนที่จะเป็นไฟล์ส่วนหัว
Anycorn

คำตอบ:


134

ตามสัญชาตญาณเมื่อคุณเชี่ยวชาญบางสิ่งอย่างเต็มที่มันไม่ได้ขึ้นอยู่กับพารามิเตอร์เทมเพลตอีกต่อไป - ดังนั้นเว้นแต่คุณจะสร้างความเชี่ยวชาญพิเศษแบบอินไลน์คุณจะต้องใส่ไว้ในไฟล์. cpp แทน. h หรือคุณจะละเมิด กฎนิยามอย่างหนึ่งตามที่เดวิดกล่าว โปรดทราบว่าเมื่อคุณเชี่ยวชาญเทมเพลตบางส่วนความเชี่ยวชาญเฉพาะบางส่วนยังคงขึ้นอยู่กับพารามิเตอร์เทมเพลตอย่างน้อยหนึ่งพารามิเตอร์ดังนั้นจึงยังคงอยู่ในไฟล์. h


อืมฉันยังงงอยู่นิดหน่อยว่ามันทำลาย ODR ได้อย่างไร เนื่องจากคุณกำหนดแม่แบบเฉพาะทางเพียงครั้งเดียว คุณอาจสร้างอ็อบเจ็กต์หลายครั้งในอ็อบเจ็กต์ไฟล์ที่แตกต่างกัน (เช่นในกรณีนี้เป็นอินสแตนซ์ใน other.c และ main.c) แต่อ็อบเจ็กต์ดั้งเดิมนั้นถูกกำหนดไว้ในไฟล์เดียวเท่านั้น - ในกรณีhello.hนี้
Justin Liang

3
@JustinLiang: ส่วนหัวจะรวมอยู่ในไฟล์. c สองไฟล์ที่แยกจากกันซึ่งมีผลเหมือนกับว่าคุณเขียนเนื้อหา (รวมถึงความเชี่ยวชาญทั้งหมด) ลงในไฟล์โดยตรงที่รวมอยู่ในตำแหน่งที่เกี่ยวข้อง กฎความหมายเดียว (ดูen.wikipedia.org/wiki/One_Definition_Rule ) กล่าวว่า (เหนือสิ่งอื่นใด): "ในโปรแกรมทั้งหมดอ็อบเจ็กต์หรือฟังก์ชันที่ไม่ใช่อินไลน์ต้องมีคำจำกัดความได้มากกว่าหนึ่งคำจำกัดความ" ในกรณีนี้ความเชี่ยวชาญพิเศษทั้งหมดของเทมเพลตฟังก์ชันนั้นมีสาระสำคัญเหมือนกับฟังก์ชันปกติดังนั้นเว้นแต่ว่าจะเป็นแบบอินไลน์ก็ไม่สามารถมีคำจำกัดความได้มากกว่าหนึ่งคำจำกัดความ
Stuart Golodetz

อืมฉันสังเกตว่าเมื่อเราไม่มีความเชี่ยวชาญด้านเทมเพลตข้อผิดพลาดนี้จะไม่ปรากฏขึ้น สมมติว่าเรามีฟังก์ชันที่แตกต่างกันสองฟังก์ชันที่กำหนดไว้ในไฟล์ส่วนหัวนอกชั้นเรียนฟังก์ชันเหล่านี้จะยังคงทำงานโดยไม่มีอินไลน์? ตัวอย่างเช่น: pastebin.com/raw.php?i=bRaiNC7M ฉันเรียนคลาสนั้นและรวมไว้ในสองไฟล์ สิ่งนี้จะไม่มี "ผลเช่นเดียวกับการที่คุณเขียนเนื้อหา" ลงในไฟล์ทั้งสองโดยตรงและจะมีข้อผิดพลาดหลายคำจำกัดความหรือไม่?
Justin Liang

@ จัสตินเหลียงรหัสส่วนหัวตามคลาสของคุณจะยังคงละเมิด ODR หากรวมอยู่ในไฟล์หลายไฟล์เว้นแต่ข้อกำหนดของฟังก์ชันจะอยู่ภายในเนื้อหาของคลาส
Hari

ดังนั้นหากนิยามสมาชิกแบบคงที่ของฉันนำหน้าtemplate <typename T>มันอาจไปอยู่ในส่วนหัวและถ้าเป็นเช่นtemplate<>นั้นก็อาจไม่ใช่?
ยีราฟสีม่วง

50

คีย์เวิร์ดinlineเป็นข้อมูลเพิ่มเติมเกี่ยวกับการบอกคอมไพลเลอร์ว่าสัญลักษณ์จะปรากฏในอ็อบเจ็กต์ไฟล์มากกว่าหนึ่งไฟล์โดยไม่ละเมิดกฎคำจำกัดความเดียวมากกว่าเกี่ยวกับอินไลน์อินไลน์จริงซึ่งคอมไพเลอร์สามารถตัดสินใจที่จะทำหรือไม่ทำ

ปัญหาที่คุณพบคือหากไม่มีอินไลน์ฟังก์ชันจะถูกคอมไพล์ในหน่วยการแปลทั้งหมดที่มีส่วนหัวซึ่งละเมิด ODR การเพิ่มinlineมีวิธีที่ถูกต้อง มิฉะนั้นคุณสามารถส่งต่อประกาศความเชี่ยวชาญและระบุเป็นหน่วยการแปลเดียวได้เช่นเดียวกับที่คุณทำกับฟังก์ชันอื่น ๆ


22

คุณได้สร้างอินสแตนซ์เทมเพลตอย่างชัดเจนในส่วนหัวของคุณ ( void Hello<T>::print_hello(T var)) สิ่งนี้จะสร้างคำจำกัดความหลายคำ คุณสามารถแก้ปัญหาได้สองวิธี:

1) สร้างอินสแตนซ์ของคุณแบบอินไลน์

2) ประกาศการสร้างอินสแตนซ์ในส่วนหัวจากนั้นนำไปใช้ใน cpp


อันที่จริงมีวิธีที่ 3 ซึ่งเป็นที่จะนำผู้ที่อยู่ใน namespace ที่ไม่มีชื่อ ... ซึ่งคล้ายกับมีคงที่ในซี
อเล็กซิส Wilke

4
ไม่ถูกต้องที่นี่ ความเชี่ยวชาญพิเศษของเทมเพลตต้องอยู่ในเนมสเปซเดียวกับเทมเพลตดั้งเดิม
Edward Strange

0

นี่คือบางส่วนของมาตรฐาน C ++ 11 ที่เกี่ยวข้องกับปัญหานี้:

ความเชี่ยวชาญอย่างชัดเจนของเทมเพลตฟังก์ชันจะเป็นแบบอินไลน์ก็ต่อเมื่อมีการประกาศด้วยตัวระบุอินไลน์หรือกำหนดเป็นลบและไม่ขึ้นอยู่กับว่าเทมเพลตฟังก์ชันเป็นแบบอินไลน์หรือไม่ [ตัวอย่าง:

เทมเพลตเป็นโมฆะ f (T) {/ * ... /} เทมเพลตอินไลน์ T g (T) {/ ... * /}

template <> inline void f <> (int) {/ * ... /} // OK: inline template <> int g <> (int) {/ ... * /} // OK: not inline - end ตัวอย่าง]

ดังนั้นถ้าคุณทำบางอย่างชัดเจน (aka เต็ม) เฉพาะของแม่แบบใน*.hไฟล์แล้วคุณยังจะต้องinlineจะช่วยให้คุณได้รับการกำจัดของการละเมิดของODR

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.