Khi viết một lớp C ++ templated, bạn thường có ba tùy chọn:
(1) Đặt khai báo và định nghĩa trong tiêu đề.
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f()
{
...
}
};
hoặc là
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f();
};
template <typename T>
inline void Foo::f()
{
...
}
Chuyên nghiệp:
- Sử dụng rất thuận tiện (chỉ bao gồm các tiêu đề).
Con:
- Giao diện và phương thức thực hiện được trộn lẫn. Đây là "chỉ" một vấn đề dễ đọc. Một số tìm thấy điều này không thể nhầm lẫn, bởi vì nó khác với cách tiếp cận .h / .cpp thông thường. Tuy nhiên, lưu ý rằng điều này không có vấn đề trong các ngôn ngữ khác, ví dụ, C # và Java.
- Tác động xây dựng lại cao: Nếu bạn khai báo một lớp mới với
Foo
tư cách là thành viên, bạn cần bao gồm foo.h
. Điều này có nghĩa là thay đổi việc thực hiện Foo::f
lan truyền thông qua cả tệp tiêu đề và tệp nguồn.
Hãy xem xét kỹ hơn về tác động xây dựng lại: Đối với các lớp C ++ không có templated, bạn đặt các khai báo trong .h và các định nghĩa phương thức trong .cpp. Theo cách này, khi việc thực hiện một phương thức được thay đổi, chỉ cần biên dịch lại một .cpp. Điều này khác với các lớp mẫu nếu .h chứa tất cả mã của bạn. Hãy xem ví dụ sau:
// 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();
}
Ở đây, cách sử dụng duy nhất Foo::f
là bên trong bar.cpp
. Tuy nhiên, nếu bạn thay đổi việc thực hiện Foo::f
, cả hai bar.cpp
và qux.cpp
cần phải được biên dịch lại. Việc thực hiện Foo::f
cuộc sống trong cả hai tệp, mặc dù không có phần nào Qux
trực tiếp sử dụng bất cứ thứ gì Foo::f
. Đối với các dự án lớn, điều này có thể sớm trở thành một vấn đề.
(2) Đặt khai báo trong .h và định nghĩa trong .tpp và đưa nó vào .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()
{
...
}
Chuyên nghiệp:
- Sử dụng rất thuận tiện (chỉ bao gồm các tiêu đề).
- Các định nghĩa giao diện và phương thức được tách ra.
Con:
- Tác động xây dựng lại cao (giống như (1) ).
Giải pháp này phân tách khai báo và định nghĩa phương thức trong hai tệp riêng biệt, giống như .h / .cpp. Tuy nhiên, cách tiếp cận này có cùng một vấn đề xây dựng lại như (1) , bởi vì tiêu đề trực tiếp bao gồm các định nghĩa phương thức.
(3) Đặt khai báo trong .h và định nghĩa trong .tpp, nhưng không bao gồm .tpp trong .h.
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f();
};
// foo.tpp
#pragma once
template <typename T>
void Foo::f()
{
...
}
Chuyên nghiệp:
- Giảm tác động xây dựng lại giống như tách .h / .cpp.
- Các định nghĩa giao diện và phương thức được tách ra.
Con:
- Sử dụng không thuận tiện: Khi thêm một
Foo
thành viên vào một lớp Bar
, bạn cần đưa foo.h
vào tiêu đề. Nếu bạn gọi Foo::f
một .cpp, bạn cũng phải bao gồm foo.tpp
ở đó.
Cách tiếp cận này làm giảm tác động xây dựng lại, vì chỉ các tệp .cpp thực sự sử dụng Foo::f
cần phải được biên dịch lại. Tuy nhiên, điều này có giá: Tất cả những tệp cần bao gồm foo.tpp
. Lấy ví dụ từ trên và sử dụng phương pháp mới:
// 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();
}
Như bạn có thể thấy, sự khác biệt duy nhất là bao gồm bổ sung foo.tpp
trong bar.cpp
. Điều này là bất tiện và thêm một giây bao gồm cho một lớp tùy thuộc vào việc bạn gọi các phương thức trên nó có vẻ rất xấu. Tuy nhiên, bạn giảm tác động xây dựng lại: Chỉ bar.cpp
cần được biên dịch lại nếu bạn thay đổi việc thực hiện Foo::f
. Các tập tin qux.cpp
không cần biên dịch lại.
Tóm lược:
Nếu bạn triển khai một thư viện, bạn thường không cần quan tâm đến việc xây dựng lại tác động. Người dùng thư viện của bạn lấy một bản phát hành và sử dụng nó và việc triển khai thư viện không thay đổi trong công việc hàng ngày của người dùng. Trong những trường hợp như vậy, thư viện có thể sử dụng cách tiếp cận (1) hoặc (2) và đó chỉ là vấn đề về hương vị mà bạn chọn.
Tuy nhiên, nếu bạn đang làm việc trên một ứng dụng hoặc nếu bạn đang làm việc trên một thư viện nội bộ của công ty, mã sẽ thay đổi thường xuyên. Vì vậy, bạn phải quan tâm về xây dựng lại tác động. Chọn cách tiếp cận (3) có thể là một lựa chọn tốt nếu bạn khiến các nhà phát triển của mình chấp nhận bổ sung.