رفتن به مطلب
جامعه‌ی برنامه‌نویسان مُدرن ایران
  • 1
قاسم رمضانی منش

استفاده از template class در فایل جداگانه

سوال

ارسال شده در (ویرایش شده)

سلام 

در صورتی که تعاریف توابع عضو کلاس (class function member definition) را در همان فایل anothertest.h قرار دهیم برنامه بدون مشکلی اجرا میشود. اما در صورتی که تعاریف توابع را به داخل فایل anothertest.cpp انتقال دهیم ، برنامه کامپایل ولی با خطای لینکر مواجه میشود. 

دلیل این اتفاق چیست ؟

--ویرایش : پس از جستجویی که انجام دادم متوجه شدم که به دلیل اینکه کامپایلر برای هر نمونه از template هایی که ما نیاز داریم یک نسخه جداگانه درست میکند. مثلا در کد زیر. کد باید به این صورت تغییر کند :

class TemplateTest{
  private :
    int ClassVariable;
  public :
    TemplateTest(const int& input);
    int ReturnClassVariable(void);
};

 

Git Diff

 #ifndef ANOTHERTEST_H
 #define ANOTHERTEST_H
 
-template <class AnotherType>
 class TemplateTest{
   private :
-    AnotherType ClassVariable;
+    int ClassVariable;
   public :
-    TemplateTest(const AnotherType& input);
-    AnotherType ReturnClassVariable(void);
+    TemplateTest(const int& input);
+    int ReturnClassVariable(void);
 };
 
 #endif // ANOTHERTEST_H

و از آنجایی که تعاریف کلاس ها در فایل دیگری قرار دارد. کامپایلر فراموش به نمونه سازی از آنها میکند. و به این دلیل است که با خطای لینکر مواجه میشویم. و برای رفع این مشکل باید تعاریف توابع را نیز در فایل anothertest.h قرار بدهیم. 

با این اوصاف آیا روش دیگه ای هست که  از template ها در فایل های جداگانه استفاده کنیم ؟

anothertest.h

#ifndef ANOTHERTEST_H
#define ANOTHERTEST_H

template <class AnotherType>
class TemplateTest{
  private :
    AnotherType ClassVariable;
  public :
    TemplateTest(const AnotherType& input);
    AnotherType ReturnClassVariable(void);
};

#endif // ANOTHERTEST_H

 

anothertest.cpp

#include "anothertest.h"

template<class AnotherType>
TemplateTest<AnotherType>::TemplateTest(const AnotherType &input) :
  ClassVariable(input){

}

template<class AnotherType>
AnotherType TemplateTest<AnotherType>::ReturnClassVariable(){
  return this->ClassVariable;
}

 

main.cpp

#include <iostream>
#include "anothertest.h"

int main(){
  TemplateTest<int> AnotherTemplate(100);
  std::cout << AnotherTemplate.ReturnClassVariable () << std::endl;
  return 0x0000;
}

 

ویرایش شده در توسط قاسم رمضانی منش

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

4 پاسخ به این سوال تا کنون داده شده است

پست های پیشنهاد شده

  • 2

با توجه به پاسخی که در مورد دلیلش یافتید، در تکمیل روش جنابِ @فرهاد شیری روش‌های مختلفی برای تعاریف کلاس‌ها از نوع template در فایل cpp  وجود دارد که قبل از C++17 روش زیر یکی از روش‌های رایج است که در آن شما کلاس را با انواع صریح تعریف می‌کنید.

کد مربوط به فایل .h به صورت زیر خواهد بود:

#ifndef ENTITY_H
#define ENTITY_H

#include <iostream>
#include <string>

template<class T>
#define ENTITY_INT      template class Entity<int>;
#define ENTITY_STRING   template class Entity<std::string>;
#define ENTITY_BOOL     template class Entity<bool>;
#define ENTITY_DOUBLE   template class Entity<double>;

/*!
 * \brief The Entity class
 */
class Entity
{
public:
    Entity();
    ~Entity();

    /*!
     * \brief Function
     * \param t
     */
    void Function(const T &t) const;

};

#endif // ENTITY_H

همچنین کد مربوط به فایل .cpp به صورت زیر پیاده سازی خواهد شد:

#include "entity.h"

template<class T>
Entity<T>::Entity()
{

}

template<class T>
Entity<T>::~Entity()
{

}

template<typename T>
void Entity<T>::Function(const T &t) const
{
    std::cout << "T = " << t << std::endl;
}

ENTITY_INT
ENTITY_STRING
ENTITY_BOOL
ENTITY_DOUBLE

روش استفاده:

#include <iostream>
#include "entity.h"


int main(void)
{

    Entity<int> *en = new Entity<int>;
    en->Function(10);
    delete en;

    Entity<double> *en2 = new Entity<double>;
    en2->Function(2000.23);
    delete en2;

    Entity<std::string> *en3 = new Entity<std::string>;
    en3->Function("Hello, World!");
    delete en3;

}

نکته: در استاندارد ۱۷ استفاده از پارامتر‌های auto نیز برای بهینه نویسی بیشتر مفید هستند.

  • پسندیدن 3

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر
  • 1

با تشکر از نکاتی که دوست عزیز قاسم رمضانی منش زحمت کشیدند واینجا قرار دادند.

معمولا وقتی کلاس را به صورت ژنریک با استفاده از template تعریف میکنیم باید پیاده سازی های کلاس هم به صورت inline تعریف شوند و البته از قوانین تعریف متدهای inline (مبنی بر اینکه دستورات متد نباید بیشتر از چند خط باشد) نباید تخطی نمایید. ودر صورتی که دستورات زیادی باید پیاده شوند در متدها حتما باید این متدها به ماژولهای کوچکتر تقسیم شوند و یا از روشهای وراثت و پلی مورفیسم در تعاریف استفاده شود.

و علت این قضیه هم کاملا مشخص است همانطور که دوست عزیز جناب آقای رمضانی منش هم اشاره کردند وقتی خود کامپایلر این جداسازی را انجام می دهد دلیلی برای انجام این کار وجود ندارد.

به عنوان مثال...

#if COMPILER == COMPILER_MSVC
#   define INLINE          inline
#   define FORCE_INLINE    __forceinline
#elif COMPILER == COMPILER_GNUC
#   define INLINE          inline
#   define FORCE_INLINE    inline  __attribute__((always_inline))
#elif COMPILER == COMPILER_CLANG || COMPILER == COMPILER_APPLECLANG
#   define INLINE          inline
#   define FORCE_INLINE    inline  __attribute__((always_inline))
#else
#   define INLINE          inline
#   define FORCE_INLINE    inline // no force inline for other platforms possible
#endif

template<typename Class_Type, typename...Ctor_Args, std::size_t... Arg_Count>
struct invoker<ctor_type, as_object, type_list<Class_Type, Ctor_Args...>, index_sequence<Arg_Count...>>
{
    using return_type = Class_Type;

    template<typename... TArgs>
    static INLINE variant invoke(TArgs&&...args)
    {
        if (check_all_true(args. template is_type<Ctor_Args>()...))
            return variant(Class_Type(args. template get_value<Ctor_Args>()...));
        else
            return variant();
    }
};

 

  • پسندیدن 2
  • تشکر شده 1

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر
  • 0

سلام

در  لینک اول کلا این سوال شما رو توضیح داده و گفته چرا این اتفاق میفته و در مورد سوال دومتون هم سه روش رو گفته که میتونید یکی رو انتخاب کنید

How to define a template class in a .h file and implement it in a .cpp file - CodeProject

  • تشکر شده 1

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر
  • 0

@Amir Balazadeh  دوست عزیز ! مرسی از پاسخِتان. ولی بهتر بود که توضیح میدادید موارد را به جای اینکه لینک ارسال کنید :).

 

روش حل این مشکل را با توجه به روش هایی که داخل ساتی CodeProject گفته شده بود میگم تا ذخیره ای برای بقیه دوستانی باشد که این سوال رو دارن.

داخل لینک سه روش گفته شده بود که دو روش رُ اینجا میگم :

به همان دلیلی که گفتم 

در 4 ساعت قبل، قاسم رمضانی منش گفته است :

دلیل اینکه کامپایلر برای هر نمونه از template هایی که ما نیاز داریم یک نسخه جداگانه درست میکند.

+ و کامپایل متوجه تعریفاتی که در فایل anothertest.cpp کردیم نمیشوند. برای رفع این مشکل ما باید یا تعاریف توابع خودمان را داخل خود کلاس قرار بدیم. یا اینکه خارج از کلاس ولی در همان فایلی که کلاس در آن هست. یعنی فایل anthertest.h. خب ! اینجا میتونیم از قابلیت های پیش‌پردازنده در سی‌پلاس‌پلاس استفاده کنیم. 

قبل از اینکه روش حل مشکل را بگم. یه نگاه به روش کار کرد این خط از کد بکنیم :

File1.h

void AnotherFunction(void){
  std::cout << "I Can't do Anything :(" << std::endl;
}

main.cpp

#include "file1.h"

int main(){
  return 0;
}

خب برای اینکه خروجی این فایل را مشاهده کنیم و متوجه بشیم که چطوری پیش‌پردازنده‌ی #include کار میکند. با استفاده از این دستور و کامپایلر gcc برنامه را کامپایل میکنیم :

[ghasem@clibcore an]$ g++ -E main.cpp > file

و حالا خروجی حاصل از دستور بالا :

[ghasem@clibcore an]$ cat file
# 1 "main.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "main.cpp"
# 1 "file1.h" 1
void AnotherFunction(void){
  std::cout << "I Can't do Anything :(" << std::endl;
}
# 2 "main.cpp" 2
int main(){
    return 0;
}

امیدوارم کار پیش‌پردازنده‌ی #include را قشنگ متوجه شده باشید :).

 

حالا برای قسمت حل مشکل template ها !.

یه راه ساده برای حل این مشکل اینکه ما با فایل anothertest.cpp را بعد از کلاس با استفاده از #include اضافه کنیم ... به اینصورت :

class TemplateTest{
  private :
    int ClassVariable;
  public :
    TemplateTest(const int& input);
    int ReturnClassVariable(void);
};
#include "anothertest.cpp"

و یک راه دیگه : هر دوفایل anothertest.cpp و anothertest.h را داخل فایل کدمان یعنی main.cpp وارد کنیم :

#include <iostream>
#include "anothertest.h"
#include "anothertest.cpp"
 
int main(){
  TemplateTest<int> AnotherTemplate(100);
  std::cout << AnotherTemplate.ReturnClassVariable () << std::endl;
  return 0x0000;
}

 

با تشکر.

  • تشکر شده 1

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

برای ارسال دیدگاه یک حساب کاربری ایجاد کنید یا وارد حساب خود شوید

برای اینکه بتوانید دیدگاهی ارسال کنید نیاز دارید که کاربر سایت شوید

ایجاد یک حساب کاربری

برای حساب کاربری جدید در سایت ما ثبت نام کنید. عضویت خیلی ساده است !

ثبت نام یک حساب کاربری جدید

ورود به حساب کاربری

دارای حساب کاربری هستید؟ از اینجا وارد شوید

ورود به حساب کاربری

  • کاربران آنلاین در این صفحه   0 کاربر

    هیچ کاربر عضوی،در حال مشاهده این صفحه نیست.

×