ความเชี่ยวชาญเทมเพลตของวิธีการเดียวจากคลาสเทมเพลต


92

พิจารณาเสมอว่าส่วนหัวต่อไปนี้ซึ่งมีคลาสเทมเพลตของฉันรวมอยู่ใน.CPPไฟล์อย่างน้อยสองไฟล์โค้ดนี้คอมไพล์อย่างถูกต้อง:

template <class T>
class TClass 
{
public:
  void doSomething(std::vector<T> * v);
};

template <class T>
void TClass<T>::doSomething(std::vector<T> * v) {
  // Do something with a vector of a generic T
}

template <>
inline void TClass<int>::doSomething(std::vector<int> * v) {
  // Do something with a vector of int's
}

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

แล้วฉันจะเอาออกได้inlineอย่างไร? รหัสไม่ควรซ้ำกันในการใช้งานทุกครั้ง ฉันค้นหา Google อ่านคำถามที่นี่ใน SO และลองใช้วิธีแก้ปัญหาที่แนะนำมากมาย แต่ไม่สามารถสร้างได้สำเร็จ (อย่างน้อยก็ไม่ใช่ใน VS 2008)

ขอบคุณ!


4
ทำไมคุณถึงต้องการลบอินไลน์? คุณคิดว่ามันทำให้คุณรู้สึกไม่พอใจหรือไม่? คุณคิดว่ามันเปลี่ยนความหมายของรหัสของคุณหรือไม่?
Martin York

1
เพราะถ้าวิธีนี้จะ "ยาว" และใช้ในหลาย ๆ ที่ฉันจะได้รับการคัดลอกรหัสไบนารีทุกที่ใช่ไหม? ฉันพยายามอธิบายสิ่งนี้ในคำถาม แต่ฉันเดาว่ามันไม่ชัดเจน ... :)
Chuim

@Martin: จะเกิดอะไรขึ้นถ้าการใช้งานต้องการโค้ดอื่น ๆ อีกมากมายที่จะต้องรวมไว้ในส่วนหัวนี้แทนไฟล์ cpp?
sbi

คำตอบ:


72

เช่นเดียวกับฟังก์ชันง่ายๆคุณสามารถใช้การประกาศและการนำไปใช้งานได้ ใส่คำประกาศส่วนหัวของคุณ:

template <>
void TClass<int>::doSomething(std::vector<int> * v);

และนำการใช้งานไปใช้ในไฟล์ cpp ของคุณ:

template <>
void TClass<int>::doSomething(std::vector<int> * v) {
 // Do somtehing with a vector of int's
}

อย่าลืมลบแบบอินไลน์ (ฉันลืมและคิดว่าวิธีนี้จะใช้ไม่ได้ :)) ตรวจสอบบน VC ++ 2005


ฉันได้ลองทำบางอย่างที่คล้ายกับสิ่งนี้มาก่อน แต่ฉันได้รับข้อผิดพลาดอื่น ๆ แต่ตอนนี้ที่คุณพูดถึงฉันคงลืมที่จะลบinlineขณะคัดลอก / วาง วิธีนี้ได้ผล!
Chuim

เช่นเดียวกับฟังก์ชันที่ไม่มีเทมเพลต (เมื่อเทียบกับเมธอดคลาส) ฉันได้รับข้อผิดพลาดตัวเชื่อมโยงเดียวกันสำหรับความเชี่ยวชาญด้านฟังก์ชันของฉัน ฉันย้ายเนื้อหาของฟังก์ชันความเชี่ยวชาญพิเศษไปยังไฟล์. cpp และออกจากการประกาศความเชี่ยวชาญในส่วนหัวและทุกอย่างก็ใช้ได้ ขอบคุณ!
aldo

ฉันเพิ่งพบปัญหานี้และข้างต้นแก้ปัญหาให้ฉันได้ นอกจากนี้คุณต้องดูแลตำแหน่งที่คอมไพลเลอร์ขยายโค้ดเทมเพลต หากทำสองครั้งคอมไพเลอร์จะบ่นเกี่ยวกับคำจำกัดความหลายคำ
Diederik

4

คุณต้องย้ายข้อกำหนดเฉพาะไปยังไฟล์ CPP อนุญาตให้ใช้ความเชี่ยวชาญของฟังก์ชันสมาชิกของคลาสเทมเพลตได้แม้ว่าฟังก์ชันจะไม่ได้ประกาศเป็นเทมเพลตก็ตาม


3

ไม่มีเหตุผลที่จะลบคำหลักแบบอินไลน์
มันไม่ได้เปลี่ยนความหมายของรหัสต่อไป


คัดลอกมาจากความคิดเห็นของคำถาม: เนื่องจากถ้าวิธีนี้จะ "ยาว" และใช้ในหลาย ๆ ที่ฉันจะได้รับรหัสไบนารีที่คัดลอกทุกที่ใช่ไหม ฉันพยายามอธิบายสิ่งนี้ในคำถาม แต่ฉันเดาว่ามันไม่ชัดเจน ... :)
Chuim

1
ไม่ผู้เชื่อมโยงจะลบสำเนาส่วนเกินใด ๆ ดังนั้นภายในแอปพลิเคชันหรือ lib คุณจะมีเพียงครั้งเดียวของวิธีการ
Martin York

3
หากinlineคีย์เวิร์ดส่งผลให้ฟังก์ชันถูกอินไลน์จริง (มาตรฐานระบุว่าคอมไพลเลอร์ควรใช้เป็นคำใบ้) สำเนาพิเศษเหล่านั้นจะไม่สามารถลบออกได้ อย่างไรก็ตามเป็นเพียงคำใบ้ในการอินไลน์เท่านั้น (ผลหลักคือการบอกว่า "อย่าสร้างข้อผิดพลาดในการชนกันของลิงก์ในลักษณะเฉพาะ")
Yakk - Adam Nevraumont

2

หากคุณต้องการลบอินไลน์ด้วยเหตุผลใดก็ตามการแก้ปัญหาของ maxim1000 นั้นใช้ได้อย่างสมบูรณ์แบบ

ในความคิดเห็นของคุณดูเหมือนว่าคุณจะเชื่อว่าคำหลักแบบอินไลน์หมายความว่าฟังก์ชันที่มีเนื้อหาทั้งหมดของเขาจะได้รับการอินไลน์เสมอ แต่ AFAIK นั้นขึ้นอยู่กับการเพิ่มประสิทธิภาพคอมไพเลอร์ของคุณเป็นอย่างมาก

อ้างจากคำถามที่พบบ่อยเกี่ยวกับC ++

มีหลายวิธีในการกำหนดว่าฟังก์ชันเป็นแบบอินไลน์ซึ่งบางวิธีเกี่ยวข้องกับคำหลักแบบอินไลน์ส่วนวิธีอื่นไม่ทำ ไม่ว่าคุณจะกำหนดฟังก์ชันเป็นแบบอินไลน์อย่างไรก็เป็นคำขอที่คอมไพลเลอร์ได้รับอนุญาตให้ละเว้น: คอมไพลเลอร์อาจอินไลน์ขยายบางส่วนทั้งหมดหรือไม่มีเลยที่คุณเรียกใช้ฟังก์ชันที่กำหนดเป็นอินไลน์ (อย่าท้อแท้หากสิ่งนั้นดูคลุมเครืออย่างสิ้นหวังความยืดหยุ่นข้างต้นเป็นข้อได้เปรียบอย่างมาก: ช่วยให้คอมไพเลอร์ปฏิบัติกับฟังก์ชันขนาดใหญ่แตกต่างจากฟังก์ชันขนาดเล็กและยังช่วยให้คอมไพเลอร์สร้างโค้ดที่ง่ายต่อการดีบั๊กหากคุณเลือก ตัวเลือกคอมไพเลอร์ที่เหมาะสม)

ดังนั้นหากคุณไม่ทราบว่าฟังก์ชันนั้นจะขยายไฟล์ปฏิบัติการของคุณได้จริงหรือเว้นแต่คุณต้องการลบออกจากส่วนหัวคำนิยามเทมเพลตด้วยเหตุผลอื่นคุณสามารถปล่อยให้มันอยู่ในที่ที่ไม่มีอันตรายใด ๆ


1

ฉันต้องการเพิ่มว่ายังคงมีเหตุผลที่ดีในการเก็บinlineคีย์เวิร์ดไว้ที่นั่นหากคุณตั้งใจที่จะปล่อยให้ความเชี่ยวชาญพิเศษในไฟล์ส่วนหัว

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

อ้างอิง: https://stackoverflow.com/a/4445772/1294184


0

นี่เป็นโอทีเล็กน้อย แต่ฉันคิดว่าจะปล่อยไว้ที่นี่เผื่อว่าจะได้ช่วยคนอื่น ฉันกำลัง googling เกี่ยวกับความเชี่ยวชาญด้านเทมเพลตซึ่งทำให้ฉันมาที่นี่และในขณะที่คำตอบของ @ maxim1000 นั้นถูกต้องและในที่สุดก็ช่วยฉันหาปัญหาได้ แต่ฉันก็ไม่คิดว่ามันจะชัดเจนมากมาย

สถานการณ์ของฉันแตกต่างกันเล็กน้อย (แต่ก็คล้ายกันมากพอที่จะปล่อยให้คำตอบนี้ฉันคิดว่า) มากกว่า OP โดยพื้นฐานแล้วฉันใช้ไลบรารีของบุคคลที่สามกับคลาสทุกประเภทที่กำหนด "ประเภทสถานะ" หัวใจของประเภทเหล่านี้เป็นเพียงแค่enums แต่คลาสทั้งหมดสืบทอดมาจากพาเรนต์ทั่วไป (นามธรรม) และมีฟังก์ชันยูทิลิตี้ที่แตกต่างกันเช่นการโอเวอร์โหลดตัวดำเนินการและstatic toString(enum type)ฟังก์ชัน แต่ละสถานะenumแตกต่างกันและไม่เกี่ยวข้องกัน ตัวอย่างเช่นหนึ่งenumมีฟิลด์NORMAL, DEGRADED, INOPERABLEอื่นมีAVAILBLE, PENDING, MISSINGฯลฯ ซอฟต์แวร์ของฉันรับผิดชอบในการจัดการสถานะประเภทต่างๆสำหรับส่วนประกอบต่างๆ มันเกี่ยวกับว่าฉันต้องการใช้toStringฟังก์ชันเหล่านี้enumชั้นเรียน แต่เนื่องจากเป็นนามธรรมฉันจึงไม่สามารถสร้างอินสแตนซ์ได้โดยตรง ฉันสามารถขยายแต่ละชั้นเรียนที่ฉันต้องการใช้ แต่ในที่สุดฉันก็ตัดสินใจที่จะสร้างtemplateชั้นเรียนซึ่งtypenameจะเป็นสถานะที่เป็นรูปธรรมที่enumฉันสนใจ อาจมีการถกเถียงกันบ้างเกี่ยวกับการตัดสินใจนั้น แต่ฉันรู้สึกว่ามันได้ผลน้อยกว่าการขยายenumคลาสนามธรรมแต่ละคลาสด้วยตัวของฉันเองและใช้ฟังก์ชันนามธรรม และแน่นอนในรหัสของฉันฉันแค่อยากจะสามารถเรียก.toString(enum type)และให้มันพิมพ์การแสดงสตริงของสิ่งenumนั้น เนื่องจากทุกenumอย่างไม่เกี่ยวข้องกันเลยต่างคนต่างมีของตัวเองtoStringฟังก์ชันที่ (หลังจากการวิจัยบางอย่างที่ฉันเรียนรู้) ต้องถูกเรียกโดยใช้ความเชี่ยวชาญพิเศษของเทมเพลต นั่นทำให้ฉันมาที่นี่ ด้านล่างนี้เป็น MCVE ของสิ่งที่ฉันต้องทำเพื่อให้งานนี้ถูกต้อง และจริงๆแล้วคำตอบของฉันแตกต่างจาก @ maxim1000 เล็กน้อย

นี่คือไฟล์ส่วนหัว (ง่ายมาก) สำหรับไฟล์enums ในความเป็นจริงแต่ละenumคลาสถูกกำหนดไว้ในไฟล์ของตัวเอง ไฟล์นี้แสดงถึงไฟล์ส่วนหัวที่ให้มาเป็นส่วนหนึ่งของไลบรารีที่ฉันใช้:

// file enums.h
#include <string>

class Enum1
{
public:
  enum EnumerationItem
  {
    BEARS1,
    BEARS2,
    BEARS3
  };

  static std::string toString(EnumerationItem e)
  {
    // code for converting e to its string representation,
    // omitted for brevity
  }
};

class Enum2
{
public:
  enum EnumerationItem
  {
    TIGERS1,
    TIGERS2,
    TIGERS3
  };

  static std::string toString(EnumerationItem e)
  {
    // code for converting e to its string representation,
    // omitted for brevity
  }
};

เพิ่มบรรทัดนี้เพื่อแยกไฟล์ถัดไปเป็นบล็อกรหัสอื่น:

// file TemplateExample.h
#include <string>

template <typename T>
class TemplateExample
{
public:
  TemplateExample(T t);
  virtual ~TemplateExample();

  // this is the function I was most concerned about. Unlike @maxim1000's
  // answer where (s)he declared it outside the class with full template
  // parameters, I was able to keep mine declared in the class just like
  // this
  std::string toString();

private:
  T type_;
};

template <typename T>
TemplateExample<T>::TemplateExample(T t)
  : type_(t)
{

}

template <typename T>
TemplateExample<T>::~TemplateExample()
{

}

ไฟล์ถัดไป

// file TemplateExample.cpp
#include <string>

#include "enums.h"
#include "TemplateExample.h"

// for each enum type, I specify a different toString method, and the
// correct one gets called when I call it on that type.
template <>
std::string TemplateExample<Enum1::EnumerationItem>::toString()
{
  return Enum1::toString(type_);
}

template <>
std::string TemplateExample<Enum2::EnumerationItem>::toString()
{
  return Enum2::toString(type_);
}

ไฟล์ถัดไป

// and finally, main.cpp
#include <iostream>
#include "TemplateExample.h"
#include "enums.h"

int main()
{
  TemplateExample<Enum1::EnumerationItem> t1(Enum1::EnumerationItem::BEARS1);
  TemplateExample<Enum2::EnumerationItem> t2(Enum2::EnumerationItem::TIGERS3);

  std::cout << t1.toString() << std::endl;
  std::cout << t2.toString() << std::endl;

  return 0;
}

และผลลัพธ์นี้:

BEARS1
TIGERS3

ไม่มีเงื่อนงำหากนี่เป็นทางออกที่ดีในการแก้ปัญหาของฉัน แต่มันใช้ได้ผลสำหรับฉัน ตอนนี้ไม่ว่าฉันจะใช้การแจงนับกี่ประเภทสิ่งที่ฉันต้องทำคือเพิ่มสองสามบรรทัดสำหรับtoStringวิธีการในไฟล์. cpp และฉันสามารถใช้toStringวิธีการที่กำหนดไว้แล้วของไลบรารีโดยไม่ต้องใช้งานด้วยตัวเองและไม่ต้องขยายแต่ละรายการenumชั้นต้องการใช้

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