Saat menulis kelas C ++ templated, Anda biasanya memiliki tiga opsi:
(1) Masukkan deklarasi dan definisi di header.
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f()
{
...
}
};
atau
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f();
};
template <typename T>
inline void Foo::f()
{
...
}
Pro:
- Penggunaan yang sangat nyaman (cukup sertakan tajuk).
Menipu:
- Implementasi antarmuka dan metode dicampur. Ini "hanya" masalah keterbacaan. Beberapa menemukan ini tidak dapat dipertahankan, karena berbeda dari pendekatan .h / .cpp yang biasa. Namun, perlu diketahui bahwa ini tidak ada masalah dalam bahasa lain, misalnya, C # dan Java.
- Dampak pembangunan kembali tinggi: Jika Anda mendeklarasikan kelas baru dengan
Foo
sebagai anggota, Anda harus menyertakan foo.h
. Ini berarti bahwa mengubah implementasi Foo::f
propagasi melalui file header dan sumber.
Mari kita melihat lebih dekat pada dampak rekondisi: Untuk kelas-kelas C ++ non-templated, Anda menempatkan deklarasi dalam .h dan definisi metode dalam .cpp. Dengan cara ini, ketika implementasi suatu metode diubah, hanya satu .cpp yang perlu dikompilasi ulang. Ini berbeda untuk kelas templat jika .h berisi semua kode Anda. Lihatlah contoh berikut:
// 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();
}
Di sini, satu-satunya penggunaan Foo::f
di dalam bar.cpp
. Namun, jika Anda mengubah implementasi Foo::f
, keduanya bar.cpp
dan qux.cpp
perlu dikompilasi ulang. Implementasi Foo::f
kehidupan di kedua file, meskipun tidak ada bagian dari Qux
langsung menggunakan apa pun Foo::f
. Untuk proyek-proyek besar, ini bisa segera menjadi masalah.
(2) Masukkan deklarasi dalam .h dan definisi dalam .tpp dan sertakan dalam .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()
{
...
}
Pro:
- Penggunaan yang sangat nyaman (cukup sertakan tajuk).
- Antarmuka dan definisi metode dipisahkan.
Menipu:
- Dampak pembangunan kembali tinggi (sama dengan (1) ).
Solusi ini memisahkan deklarasi dan definisi metode dalam dua file terpisah, sama seperti .h / .cpp. Namun, pendekatan ini memiliki masalah pembangunan kembali yang sama dengan (1) , karena header langsung memasukkan definisi metode.
(3) Masukkan deklarasi dalam .h dan definisi dalam .tpp, tapi jangan sertakan .tpp dalam .h.
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f();
};
// foo.tpp
#pragma once
template <typename T>
void Foo::f()
{
...
}
Pro:
- Mengurangi dampak rekondisi sama seperti pemisahan .h / .cpp.
- Antarmuka dan definisi metode dipisahkan.
Menipu:
- Penggunaan tidak nyaman: Saat menambahkan
Foo
anggota ke kelas Bar
, Anda harus memasukkan foo.h
dalam header. Jika Anda memanggil Foo::f
.cpp, Anda juga harus memasukkannya foo.tpp
.
Pendekatan ini mengurangi dampak pembangunan kembali, karena hanya file .cpp yang benar-benar digunakan Foo::f
perlu dikompilasi ulang. Namun, ini ada harganya: Semua file itu perlu disertakan foo.tpp
. Ambil contoh dari atas dan gunakan pendekatan baru:
// 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();
}
Seperti yang Anda lihat, satu-satunya perbedaan adalah tambahan termasuk foo.tpp
dalam bar.cpp
. Ini tidak nyaman dan menambahkan menyertakan kedua untuk kelas tergantung pada apakah Anda memanggil metode itu tampak sangat jelek. Namun, Anda mengurangi dampak pembangunan kembali: Hanya bar.cpp
perlu dikompilasi ulang jika Anda mengubah implementasi Foo::f
. File qux.cpp
tidak perlu dikompilasi ulang.
Ringkasan:
Jika Anda menerapkan pustaka, Anda biasanya tidak perlu peduli untuk membangun kembali dampak. Pengguna perpustakaan Anda mengambil rilis dan menggunakannya dan implementasi perpustakaan tidak berubah dalam pekerjaan sehari-hari pengguna. Dalam kasus seperti itu, perpustakaan dapat menggunakan pendekatan (1) atau (2) dan itu hanya masalah selera mana yang Anda pilih.
Namun, jika Anda mengerjakan aplikasi, atau jika Anda bekerja di perpustakaan internal perusahaan Anda, kode sering berubah. Jadi, Anda harus peduli tentang dampak pembangunan kembali. Memilih pendekatan (3) bisa menjadi pilihan yang baik jika Anda membuat pengembang Anda menerima tambahan yang disertakan.