Şablonlu bir sınıftan tek bir yöntemin şablon uzmanlığı


92

Her zaman benim templated sınıfımı içeren aşağıdaki başlığın en az iki .CPPdosyaya dahil edildiği düşünülürse , bu kod doğru şekilde derlenir:

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
}

Ancak uzmanlaşma yöntemindeki satır içi nota dikkat edin. Yöntemin bir kereden fazla tanımlanması nedeniyle bir bağlayıcı hatasından (VS2008'de LNK2005) kaçınılması gerekir. Bunu anlıyorum çünkü AFAIK tam bir şablon uzmanlığı basit bir yöntem tanımı ile aynıdır.

Peki bunu nasıl kaldırırım inline? Kod, her kullanımında kopyalanmamalıdır. Google'da arama yaptım, SO'da bazı soruları okudum ve önerilen çözümlerin çoğunu denedim ancak hiçbiri başarılı bir şekilde oluşturulmadı (en azından VS 2008'de değil).

Teşekkürler!


4
Satırı neden kaldırmak istiyorsunuz? Estetik olarak rahatsız edici buluyor musunuz? Kodunuzun anlamını değiştirdiğini düşünüyor musunuz?
Martin York

1
Çünkü bu yöntem "uzun" olsaydı ve birçok yerde kullanılırsa, ikili kodunu her yere kopyalardım, değil mi? Bunu soruda açıklamaya çalıştım ama sanırım net değildi ... :)
Chuim

@Martin: Ya uygulama daha sonra cpp dosyası yerine bu başlık tarafından dahil edilmesi gereken çok sayıda başka koda ihtiyaç duyarsa?
sbi

Yanıtlar:


72

Basit işlevlerde olduğu gibi, bildirimi ve uygulamayı kullanabilirsiniz. Başlık beyanınızı girin:

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

ve uygulamayı cpp dosyalarınızdan birine koyun:

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

Satır içi kaldırmayı unutmayın (Bu çözümün işe yaramayacağını unuttum ve düşündüm :)). VC ++ 2005'te kontrol edildi


Daha önce en azından buna benzer bir şey denedim ama başka hatalar aldım ama şimdi bahsettiğinize göre inlinewhile kopyalama / yapıştırma işlemini kaldırmayı unutmuş olmalıyım . Bu şekilde çalıştı!
Chuim

Aynısı şablondan bağımsız işlevler için de geçerlidir (sınıf yöntemlerinin aksine). İşlev uzmanlığım için aynı bağlayıcı hatasını alıyordum. İşlev uzmanlığının gövdesini bir .cpp dosyasına taşıdım ve uzmanlık bildirimini başlıkta bıraktım ve her şey çalıştı. Teşekkürler!
aldo

Bu problemle karşılaştım ve yukarıdakiler benim için çözdü. Ek olarak, derleyicinin şablon kodunu nerede genişlettiğine dikkat etmeniz gerekir . İki kez yapılırsa, derleyici birden çok tanımdan şikayet eder.
Diederik

4

Uzmanlık tanımını CPP dosyasına taşımanız gerekir. İşlev şablon olarak bildirilmemiş olsa bile şablon sınıfının üye işlevinin uzmanlaşmasına izin verilir.


3

Satır içi anahtar kelimeyi kaldırmak için hiçbir neden yoktur.
Zaten kodun anlamını değiştirmez.


Soru yorumundan kopyalandı: Çünkü bu yöntem "uzun" olsaydı ve birçok yerde kullanılırsa, ikili kodunu her yere kopyalarım, değil mi? Bunu soruda açıklamaya çalıştım ama sanırım net değildi ... :)
Chuim

1
Hayır. Bağlayıcı, fazladan kopyaları kaldırır. Yani bir uygulama veya kitaplık içinde yöntemin yalnızca bir kez örneğine sahip olursunuz.
Martin York

3
Eğer inlineanahtar kelime fonksiyonun gerçekten satır içine alınmasıyla sonuçlanırsa (standart, derleyicinin bunu bir ipucu olarak alması gerektiğini söyler), bu durumda fazladan kopyalar kaldırılamaz. Bununla birlikte, yalnızca satır içi için bir ipucudur (birincil etkisi "belirli bir şekilde bağlantı çarpışmalarında hata oluşturmayın" demek)
Yakk - Adam Nevraumont

2

Her ne sebeple olursa olsun satırı kaldırmak isterseniz maxim1000 çözümü tamamen geçerlidir.

Yorumunuzda, yine de, satır içi anahtar kelimenin tüm içeriğine sahip işlevin her zaman satır içi olduğu, ancak aslında derleyicinizin optimizasyonuna çok bağlı olan AFAIK anlamına geldiğine inanıyorsunuz.

C ++ SSS'den alıntı yapma

Bir işlevin satır içi olduğunu belirlemenin birkaç yolu vardır, bunlardan bazıları satır içi anahtar kelimeyi içerirken diğerleri içermez. Bir işlevi satır içi olarak nasıl atarsanız atayın, derleyicinin göz ardı etmesine izin verilmesi istenir: derleyici satır içi olarak atanmış bir işlevi çağırdığınız yerlerden bazılarını, tümünü veya hiçbirini satır içi olarak genişletebilir. (Bu umutsuzca belirsiz görünüyorsa cesaretiniz kırılmasın. Yukarıdakinin esnekliği aslında çok büyük bir avantajdır: derleyicinin büyük işlevleri küçük işlevlerden farklı şekilde işlemesine olanak tanır, ayrıca derleyicinin seçerseniz hata ayıklaması kolay bir kod oluşturmasına olanak tanır. doğru derleyici seçenekleri.)

Bu nedenle, bu işlevin çalıştırılabilir dosyanızı gerçekten şişireceğini bilmiyorsanız veya başka nedenlerle şablon tanımlama başlığından kaldırmak istemiyorsanız, aslında herhangi bir zarar vermeden olduğu yerde bırakabilirsiniz.


1

inlineUzmanlığı başlık dosyasında da bırakmak istiyorsanız , anahtar kelimeyi orada tutmak için hala iyi bir neden olduğunu eklemek isterim .

"Sezgisel olarak, bir şeyi tamamen uzmanlaştırdığınızda, bu artık bir şablon parametresine bağlı değildir - bu nedenle, uzmanlaşmayı satır içi yapmazsanız, onu .h yerine .cpp dosyasına koymanız gerekir veya ihlalde bulunursunuz. tek tanım kuralı ... "

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


0

Bu biraz fazla uzatma, ama başka birine yardım etme ihtimaline karşı bunu burada bırakacağımı düşündüm. Beni buraya yönlendiren şablon uzmanlığı hakkında googling yapıyordum ve @ maxim1000'in cevabı doğru ve nihayetinde sorunlarımı çözmeme yardımcı olsa da, bunun çok net olduğunu düşünmedim.

Durumum OP'lerden biraz farklı (ama sanırım bu yanıtı bırakacak kadar benzer). Temel olarak, "durum türlerini" tanımlayan tüm farklı sınıf türlerine sahip bir üçüncü taraf kitaplığı kullanıyorum. Bu türlerin kalbi basitçe enums'dir, ancak sınıfların tümü ortak (soyut) bir ebeveynden miras alır ve operatör aşırı yükleme ve bir static toString(enum type)işlev gibi farklı yardımcı işlevler sağlar. Her durum enumbirbirinden farklıdır ve ilgisizdir. Örneğin, birinde enumalanlar var NORMAL, DEGRADED, INOPERABLE, diğerinde var AVAILBLE, PENDING, MISSING, vb. Yazılımım, farklı bileşenler için farklı tür durumları yönetmekten sorumludur. Bunun için toStringişlevleri kullanmak istediğim ortaya çıktı.enumsınıflar, ancak soyut oldukları için onları doğrudan örnekleyemedim. Kullanmak istediğim her sınıfı genişletebilirdim, ama nihayetinde önemsediğim somut statünün olacağı bir templatesınıf oluşturmaya karar verdim . Muhtemelen bu karar hakkında bazı tartışmalar olabilir, ancak bunun her soyut sınıfı kendi özel sınıfımla genişletmekten ve soyut işlevleri uygulamaktan çok daha az iş olduğunu hissettim . Ve tabii ki kodumda, sadece arayabilmek ve bunun dize temsilini yazdırmasını istedim . Hepsi birbiriyle alakasız olduğundan, her birinin kendine aittypenameenumenum.toString(enum type)enumenumtoString(öğrendiğim bazı araştırmalardan sonra) şablon uzmanlığı kullanılarak çağrılması gereken işlevler. Bu beni buraya getirdi. Aşağıda, bunun doğru bir şekilde çalışmasını sağlamak için ne yapmam gerektiğinin bir MCVE'si var. Ve aslında benim çözümüm @ maxim1000'den biraz farklıydı.

Bu, e-postalar için (büyük ölçüde basitleştirilmiş) bir başlık dosyasıdır enum. Gerçekte, her enumsınıf kendi dosyasında tanımlanmıştır. Bu dosya, kullandığım kitaplığın bir parçası olarak bana sağlanan başlık dosyalarını temsil ediyor:

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

bu satırı sadece sonraki dosyayı farklı bir kod bloğuna ayırmak için eklemek:

// 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()
{

}

sonraki dosya

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

sonraki dosya

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

ve bu çıktı:

BEARS1
TIGERS3

Bunun sorunumu çözmek için ideal çözüm olup olmadığına dair hiçbir fikrim yok, ama benim için çalıştı. Şimdi, kaç numaralandırma türü kullandığım önemli değil, tek yapmam gereken toString.cpp dosyasındaki yöntem için birkaç satır eklemek ve zaten tanımlanmış olan kitaplıkları toString, kendim uygulamadan ve her birini genişletmeden kullanabilirim. enumsınıf kullanmak istiyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.