Spesialisasi template metode tunggal dari kelas template

92

Selalu mempertimbangkan bahwa tajuk berikut, yang berisi kelas templat saya, termasuk dalam setidaknya dua .CPPfile, kode ini terkompilasi dengan benar:

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
}

Namun perhatikan inline dalam metode spesialisasi. Hal ini diperlukan untuk menghindari kesalahan linker (dalam VS2008 adalah LNK2005) karena metode tersebut didefinisikan lebih dari sekali. Saya memahami ini karena AFAIK spesialisasi template lengkap sama dengan definisi metode sederhana.

Jadi, bagaimana cara menghapusnya inline? Kode tidak boleh diduplikasi dalam setiap penggunaannya. Saya telah mencari di Google, membaca beberapa pertanyaan di SO dan mencoba banyak solusi yang disarankan tetapi tidak ada yang berhasil dibuat (setidaknya tidak di VS 2008).

Terima kasih!

Chuim
sumber
4
Mengapa Anda ingin menghapus sebaris? Apakah menurut Anda itu tidak menyenangkan secara estetika? Apakah menurut Anda itu mengubah arti kode Anda?
Martin York
1
Karena jika metode ini akan "panjang" dan digunakan di banyak tempat, saya akan mendapatkan kode binernya disalin di mana-mana, bukan? Saya mencoba menjelaskan hal ini dalam pertanyaan tetapi saya kira itu tidak jelas ... :)
Chuim
@Martin: Bagaimana jika penerapannya membutuhkan banyak kode lain yang kemudian harus disertakan oleh tajuk ini daripada file cpp?
sbi

Jawaban:

72

Seperti fungsi sederhana, Anda dapat menggunakan deklarasi dan implementasi. Masukkan deklarasi header Anda:

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

dan terapkan implementasi ke salah satu file cpp Anda:

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

Jangan lupa untuk menghapus inline (saya lupa dan mengira solusi ini tidak akan berhasil :)). Diperiksa di VC ++ 2005

maksim1000
sumber
Saya memang mencoba sesuatu yang setidaknya mirip dengan ini sebelumnya tetapi saya mendapat kesalahan lain tetapi sekarang Anda menyebutkan bahwa saya pasti lupa untuk menghapus inlinesaat menyalin / menempel. Cara ini berhasil!
Chuim
Hal yang sama berlaku untuk fungsi templat gratis (sebagai lawan metode kelas). Saya mendapatkan kesalahan tautan yang sama untuk spesialisasi fungsi saya. Saya memindahkan badan spesialisasi fungsi ke dalam file .cpp dan meninggalkan deklarasi spesialisasi di header, dan semuanya berfungsi. Terima kasih!
aldo
Saya baru saja mengalami masalah ini, dan hal di atas menyelesaikannya untuk saya. Selain itu, Anda perlu berhati-hati di mana kompiler memperluas kode template. Jika ini dilakukan dua kali, kompilator akan mengeluh tentang banyak definisi.
Diederik
4

Anda perlu memindahkan definisi spesialisasi ke file CPP. Spesialisasi fungsi anggota kelas template diperbolehkan meskipun fungsi tidak dideklarasikan sebagai template.

BostonLogan
sumber
3

Tidak ada alasan untuk menghapus kata kunci sebaris.
Itu tidak mengubah arti kode dengan cara apapun.

Martin York
sumber
Disalin dari komentar pertanyaan: Karena jika metode ini akan "panjang" dan digunakan di banyak tempat, saya akan menyalin kode binernya di mana-mana, bukan? Saya mencoba menjelaskan hal ini dalam pertanyaan tetapi saya kira itu tidak jelas ... :)
Chuim
1
Tidak. Linker menghapus salinan tambahan. Jadi dalam aplikasi atau lib Anda hanya akan memiliki satu kali contoh metode.
Martin York
3
Jika inlinekata kunci menghasilkan fungsi yang sebenarnya sebaris (standar mengatakan bahwa kompilator harus menganggapnya sebagai petunjuk), maka salinan tambahan tersebut tidak dapat dihapus. Namun demikian, ini hanya petunjuk untuk sebaris (efek utamanya adalah mengatakan "jangan menghasilkan kesalahan pada tabrakan tautan dengan cara tertentu")
Yakk - Adam Nevraumont
2

Jika Anda ingin menghapus sebaris karena alasan apa pun, solusi maksim1000 benar-benar valid.

Namun, dalam komentar Anda, tampaknya Anda percaya bahwa kata kunci inline berarti bahwa fungsi dengan semua isinya selalu sebaris tetapi AFAIK yang sebenarnya sangat bergantung pada pengoptimalan kompiler Anda.

Mengutip dari C ++ FAQ

Ada beberapa cara untuk menetapkan bahwa suatu fungsi sebaris, beberapa di antaranya melibatkan kata kunci sebaris, yang lainnya tidak. Tidak peduli bagaimana Anda menetapkan suatu fungsi sebagai sebaris, itu adalah permintaan yang boleh diabaikan oleh kompilator: kompilator mungkin memperluas-sebaris beberapa, semua, atau tidak ada tempat di mana Anda memanggil fungsi yang ditunjuk sebagai sebaris. (Jangan berkecil hati jika itu tampak sangat kabur. Fleksibilitas di atas sebenarnya merupakan keuntungan besar: ini memungkinkan kompilator memperlakukan fungsi besar secara berbeda dari yang kecil, ditambah itu memungkinkan kompilator menghasilkan kode yang mudah untuk di-debug jika Anda memilih opsi kompiler yang tepat.)

Jadi, kecuali Anda tahu bahwa fungsi itu benar-benar akan membengkak yang dapat dieksekusi atau kecuali Anda ingin menghapusnya dari tajuk definisi templat karena alasan lain, Anda benar-benar dapat membiarkannya di tempatnya tanpa membahayakan.

Triskeldeian
sumber
1

Saya ingin menambahkan bahwa masih ada alasan bagus untuk menyimpan inlinekata kunci di sana jika Anda juga ingin meninggalkan spesialisasi di file header.

"Secara intuitif, ketika Anda sepenuhnya mengkhususkan sesuatu, hal itu tidak lagi bergantung pada parameter kerangka - jadi kecuali Anda membuat spesialisasi sebaris, Anda harus meletakkannya dalam file .cpp, bukan .h atau Anda akhirnya melanggar aturan satu definisi ... "

Referensi: https://stackoverflow.com/a/4445772/1294184

Yordania
sumber
0

Ini sedikit OT, tapi saya pikir saya akan meninggalkan ini di sini kalau-kalau ini membantu orang lain. Saya sedang mencari di Google tentang spesialisasi template yang membawa saya ke sini, dan sementara jawaban @ maxim1000 benar dan pada akhirnya membantu saya menemukan masalah saya, saya tidak berpikir itu sangat jelas.

Situasi saya sedikit berbeda (tetapi saya pikir cukup mirip untuk meninggalkan jawaban ini) daripada OP. Pada dasarnya, saya menggunakan pustaka pihak ketiga dengan semua jenis kelas yang berbeda yang mendefinisikan "tipe status". Inti dari jenis ini hanyalah enums, tetapi semua kelas mewarisi dari induk umum (abstrak) dan menyediakan fungsi utilitas yang berbeda, seperti kelebihan beban operator dan static toString(enum type)fungsi. Setiap status enumberbeda satu sama lain dan tidak terkait. Misalnya, yang satu enummemiliki kolom NORMAL, DEGRADED, INOPERABLE, yang lain memiliki AVAILBLE, PENDING, MISSING, dll. Perangkat lunak saya bertugas mengelola berbagai jenis status untuk berbagai komponen. Ternyata saya ingin memanfaatkan toStringfungsi untuk inienumkelas, tetapi karena mereka abstrak saya tidak bisa membuat instance secara langsung. Saya dapat memperluas setiap kelas yang ingin saya gunakan, tetapi akhirnya saya memutuskan untuk membuat templatekelas, di mana typenamestatus konkret apa pun yang enumsaya pedulikan. Mungkin beberapa perdebatan dapat terjadi tentang keputusan itu, tetapi saya merasa itu adalah pekerjaan yang jauh lebih sedikit daripada memperluas setiap enumkelas abstrak dengan kelas kustom saya sendiri dan menerapkan fungsi abstrak. Dan tentu saja dalam kode saya, saya hanya ingin dapat memanggil .toString(enum type)dan mencetak representasi string itu enum. Karena semua enumitu sama sekali tidak terkait, mereka masing-masing memiliki miliknya sendiritoString fungsi yang (setelah beberapa penelitian yang saya pelajari) harus dipanggil menggunakan spesialisasi template. Itu membawaku ke sini. Di bawah ini adalah MCVE dari apa yang harus saya lakukan untuk membuat ini bekerja dengan benar. Dan sebenarnya solusi saya sedikit berbeda dari @ maxim1000.

Ini adalah file header (sangat disederhanakan) untuk enums. Pada kenyataannya, setiap enumkelas didefinisikan dalam filenya sendiri. File ini mewakili file header yang diberikan kepada saya sebagai bagian dari pustaka yang saya gunakan:

// 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
  }
};

menambahkan baris ini hanya untuk memisahkan file berikutnya ke dalam blok kode yang berbeda:

// 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 berikutnya

// 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_);
}

file berikutnya

// 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;
}

dan keluaran ini:

BEARS1
TIGERS3

Tidak tahu apakah ini adalah solusi ideal untuk menyelesaikan masalah saya, tetapi berhasil untuk saya. Sekarang, tidak peduli berapa banyak jenis enumerasi yang akhirnya saya gunakan, yang harus saya lakukan hanyalah menambahkan beberapa baris untuk toStringmetode di file .cpp, dan saya dapat menggunakan metode perpustakaan yang sudah ditentukan toStringtanpa menerapkannya sendiri dan tanpa memperluas masing-masing enumkelas yang ingin saya gunakan.

ya Tidak
sumber