เมื่อเขียนคลาส C ++ templated คุณมักจะมีสามตัวเลือก:
(1) ใส่การประกาศและคำนิยามในส่วนหัว
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f()
{
...
}
};
หรือ
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f();
};
template <typename T>
inline void Foo::f()
{
...
}
มือโปร:
- การใช้งานที่สะดวกมาก (รวมเฉพาะส่วนหัว)
Con:
- มีการผสมผสานอินเตอร์เฟสและวิธีการใช้งาน นี่เป็นเพียงปัญหาการอ่าน บางคนพบว่าไม่สามารถรักษาได้เพราะมันแตกต่างจากวิธีการ. h / .cpp ปกติ อย่างไรก็ตามโปรดทราบว่านี่ไม่มีปัญหาในภาษาอื่นเช่น C # และ Java
- สูงสร้างผลกระทบ: ถ้าคุณประกาศคลาสใหม่กับเป็นสมาชิกคุณต้องรวม
Foo
foo.h
ซึ่งหมายความว่าการเปลี่ยนการใช้งานของFoo::f
การเผยแพร่ผ่านทั้งส่วนหัวและไฟล์ต้นฉบับ
ให้ดูที่การสร้างใหม่อีกครั้ง: สำหรับคลาส C ++ ที่ไม่ใช่เท็มเพลตคุณใส่การประกาศใน. h และนิยามเมธอดใน. cpp วิธีนี้เมื่อมีการเปลี่ยนแปลงวิธีการใช้งานจะต้องมีการคอมไพล์. cpp เพียงอันเดียวเท่านั้น สิ่งนี้แตกต่างกันสำหรับคลาสเทมเพลตหาก. h มีรหัสทั้งหมดของคุณ ลองดูตัวอย่างต่อไปนี้:
// bar.h
#pragma once
#include "foo.h"
struct Bar
{
void b();
Foo<int> foo;
};
// bar.cpp
#include "bar.h"
void Bar::b()
{
foo.f();
}
// qux.h
#pragma once
#include "bar.h"
struct Qux
{
void q();
Bar bar;
}
// qux.cpp
#include "qux.h"
void Qux::q()
{
bar.b();
}
นี่คือการใช้งานเพียงคนเดียวที่อยู่ภายในFoo::f
bar.cpp
อย่างไรก็ตามหากคุณเปลี่ยนการใช้งานFoo::f
ทั้งสองbar.cpp
และqux.cpp
จำเป็นต้องคอมไพล์ใหม่ การดำเนินงานของFoo::f
ชีวิตในทั้งสองไฟล์แม้ว่าส่วนหนึ่งของการไม่มีโดยตรงใช้อะไรQux
Foo::f
สำหรับโครงการขนาดใหญ่สิ่งนี้อาจกลายเป็นปัญหาได้ในไม่ช้า
(2) ใส่การประกาศใน. h และคำจำกัดความเป็น. tpp และรวมไว้ใน. h
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f();
};
#include "foo.tpp"
// foo.tpp
#pragma once // not necessary if foo.h is the only one that includes this file
template <typename T>
inline void Foo::f()
{
...
}
มือโปร:
- การใช้งานที่สะดวกมาก (รวมเฉพาะส่วนหัว)
- นิยามอินเตอร์เฟสและเมธอดถูกแยกออก
Con:
- ผลกระทบการสร้างใหม่สูง (เช่นเดียวกับ(1) )
โซลูชันนี้แยกการประกาศและคำนิยามวิธีการในสองไฟล์แยกกันเช่น. h / .cpp อย่างไรก็ตามวิธีการนี้มีปัญหาการสร้างใหม่เช่นเดียวกับ(1)เนื่องจากส่วนหัวมีคำจำกัดความของวิธีการโดยตรง
(3) ใส่การประกาศใน. h และคำจำกัดความเป็น. tpp แต่ไม่รวม. tpp ใน. h
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f();
};
// foo.tpp
#pragma once
template <typename T>
void Foo::f()
{
...
}
มือโปร:
- ลดผลกระทบของการสร้างใหม่เช่นเดียวกับการแยก. h / .cpp
- นิยามอินเตอร์เฟสและเมธอดถูกแยกออก
Con:
- การใช้งานไม่สะดวก: เมื่อเพิ่ม
Foo
สมาชิกในคลาสBar
คุณต้องรวมfoo.h
ไว้ในส่วนหัว ถ้าคุณโทรFoo::f
ใน .cpp คุณยังต้องรวมถึงการfoo.tpp
มี
วิธีการนี้จะช่วยลดผลกระทบของการสร้างใหม่เนื่องจากไฟล์. cpp ที่Foo::f
ต้องใช้จริงๆจะต้องทำการคอมไพล์ใหม่ แต่นี้มาในราคา: foo.tpp
ไฟล์ทั้งหมดที่จำเป็นต้องมี นำตัวอย่างจากด้านบนและใช้วิธีการใหม่:
// bar.h
#pragma once
#include "foo.h"
struct Bar
{
void b();
Foo<int> foo;
};
// bar.cpp
#include "bar.h"
#include "foo.tpp"
void Bar::b()
{
foo.f();
}
// qux.h
#pragma once
#include "bar.h"
struct Qux
{
void q();
Bar bar;
}
// qux.cpp
#include "qux.h"
void Qux::q()
{
bar.b();
}
ในขณะที่คุณสามารถมองเห็นความแตกต่างเพียงอย่างเดียวคือเพิ่มเติมรวมถึงของในfoo.tpp
bar.cpp
สิ่งนี้ไม่สะดวกและการเพิ่มการรวมครั้งที่สองสำหรับชั้นเรียนขึ้นอยู่กับว่าคุณเรียกใช้วิธีการที่ดูเหมือนว่าน่าเกลียดมาก อย่างไรก็ตามคุณลดการสร้างผลกระทบ: เฉพาะจะต้องมีการคอมถ้าคุณเปลี่ยนการดำเนินงานของbar.cpp
Foo::f
ไฟล์qux.cpp
ไม่จำเป็นต้องคอมไพล์ใหม่
สรุป:
หากคุณใช้ไลบรารีคุณไม่จำเป็นต้องสนใจเรื่องการสร้างผลกระทบซ้ำ ผู้ใช้ไลบรารีของคุณคว้ารีลีสและใช้งานและการใช้ไลบรารี่จะไม่เปลี่ยนแปลงในการทำงานประจำวันของผู้ใช้ ในกรณีเช่นนี้ห้องสมุดสามารถใช้วิธีการ(1)หรือ(2)และเป็นเรื่องของรสนิยมที่คุณเลือก
อย่างไรก็ตามหากคุณกำลังทำงานกับแอพพลิเคชั่นหรือหากคุณกำลังทำงานกับห้องสมุดภายในของ บริษัท ของคุณรหัสจะเปลี่ยนไปบ่อยครั้ง ดังนั้นคุณต้องใส่ใจกับการสร้างผลกระทบใหม่ การเลือกวิธีการ(3)อาจเป็นตัวเลือกที่ดีหากคุณให้นักพัฒนาของคุณยอมรับข้อเสนอเพิ่มเติม