Một trong những vấn đề của pimpl là hình phạt hiệu năng của việc sử dụng nó (cấp phát bộ nhớ bổ sung, thành viên dữ liệu không liền kề, bổ sung bổ sung, v.v.). Tôi muốn đề xuất một biến thể về thành ngữ pimpl sẽ tránh các hình phạt hiệu suất này với chi phí không nhận được tất cả các lợi ích của pimpl. Ý tưởng là để lại tất cả các thành viên dữ liệu riêng tư trong lớp và chỉ di chuyển các phương thức riêng tư sang lớp pimpl. Lợi ích so với pimpl cơ bản là bộ nhớ vẫn tiếp giáp nhau (không có chỉ định bổ sung). Những lợi ích so với việc không sử dụng pimpl là:
- Nó ẩn các chức năng riêng tư.
- Bạn có thể cấu trúc nó để tất cả các chức năng này sẽ có liên kết nội bộ và cho phép trình biên dịch tối ưu hóa mạnh mẽ hơn nó.
Vì vậy, ý tưởng của tôi là làm cho pimpl thừa hưởng từ chính lớp học (nghe có vẻ hơi điên rồ tôi biết, nhưng hãy chịu đựng tôi). Nó sẽ trông giống như thế này:
Trong tập tin Ah:
class A
{
A();
void DoSomething();
protected: //All private stuff have to be protected now
int mData1;
int mData2;
//Not even a mention of a PImpl in the header file :)
};
Trong tệp A.cpp:
#define PCALL (static_cast<PImpl*>(this))
namespace //anonymous - guarantees internal linkage
{
struct PImpl : public A
{
static_assert(sizeof(PImpl) == sizeof(A),
"Adding data members to PImpl - not allowed!");
void DoSomething1();
void DoSomething2();
//No data members, just functions!
};
void PImpl::DoSomething1()
{
mData1 = bar(mData2); //No Problem: PImpl sees A's members as it's own
DoSomething2();
}
void PImpl::DoSomething2()
{
mData2 = baz();
}
}
A::A(){}
void A::DoSomething()
{
mData2 = foo();
PCALL->DoSomething1(); //No additional indirection, everything can be completely inlined
}
Theo như tôi thấy thì hoàn toàn không có hình phạt về hiệu suất khi sử dụng cái này so với không có pimpl và một số hiệu suất có thể đạt được và giao diện tệp tiêu đề sạch hơn. Một nhược điểm này có so với pimpl tiêu chuẩn là bạn không thể ẩn các thành viên dữ liệu để các thay đổi đối với các thành viên dữ liệu đó sẽ vẫn kích hoạt việc biên dịch lại mọi thứ phụ thuộc vào tệp tiêu đề. Nhưng theo cách tôi nhìn thấy, nó sẽ mang lại lợi ích đó hoặc lợi ích hiệu suất của việc các thành viên tiếp giáp nhau trong bộ nhớ (hoặc thực hiện hack này- "Tại sao Nỗ lực số 3 là đáng tin cậy"). Một cảnh báo khác là nếu A là một lớp templated, cú pháp sẽ gây khó chịu (bạn biết, bạn không thể sử dụng trực tiếp mData1, bạn cần phải làm điều này-> mData1 và bạn cần bắt đầu sử dụng tên kiểu chữ và có thể từ khóa mẫu cho các loại phụ thuộc và các kiểu templated, vv). Một cảnh báo khác là bạn không còn có thể sử dụng riêng tư trong lớp gốc, chỉ các thành viên được bảo vệ, vì vậy bạn không thể hạn chế quyền truy cập từ bất kỳ lớp kế thừa nào, không chỉ là pimpl. Tôi đã cố gắng nhưng không thể khắc phục được vấn đề này. Ví dụ, tôi đã thử biến pimpl thành một lớp mẫu bạn bè với hy vọng làm cho khai báo bạn bè đủ rộng để cho phép tôi xác định lớp pimpl thực tế trong một không gian tên ẩn danh, nhưng điều đó không hiệu quả. Nếu bất cứ ai có bất kỳ ý tưởng nào về cách giữ các thành viên dữ liệu riêng tư mà vẫn cho phép một lớp pimpl kế thừa được xác định trong một tên nơi ẩn danh để truy cập vào các thành viên đó, tôi thực sự muốn thấy nó! Điều đó sẽ loại bỏ đặt phòng chính của tôi từ việc sử dụng này.
Mặc dù vậy, tôi cảm thấy rằng những cảnh báo này được chấp nhận vì lợi ích của những gì tôi đề xuất.
Tôi đã thử tìm kiếm trực tuyến một số tài liệu tham khảo về thành ngữ "chỉ có chức năng" này nhưng không thể tìm thấy bất cứ điều gì. Tôi thực sự quan tâm đến những gì mọi người nghĩ về điều này. Có vấn đề nào khác với điều này hoặc lý do tôi không nên sử dụng điều này?
CẬP NHẬT:
Tôi đã tìm thấy đề xuất này rằng ít nhiều cố gắng hoàn thành chính xác những gì tôi đang có, nhưng thực hiện bằng cách thay đổi tiêu chuẩn. Tôi hoàn toàn đồng ý với đề xuất đó và hy vọng nó sẽ biến nó thành tiêu chuẩn (tôi không biết gì về quy trình đó nên tôi không biết khả năng đó sẽ xảy ra như thế nào). Tôi muốn có nhiều hơn để làm điều này thông qua một cơ chế ngôn ngữ được xây dựng. Đề xuất cũng giải thích những lợi ích của những gì tôi đang cố gắng đạt được tốt hơn tôi nhiều. Nó cũng không có vấn đề phá vỡ đóng gói như đề xuất của tôi (riêng tư -> được bảo vệ). Tuy nhiên, cho đến khi đề xuất đó biến nó thành tiêu chuẩn (nếu điều đó từng xảy ra), tôi nghĩ đề xuất của tôi làm cho nó có thể nhận được những lợi ích đó, với những cảnh báo mà tôi đã liệt kê.
CẬP NHẬT2:
Một trong những câu trả lời đề cập đến LTO như một giải pháp thay thế khả thi để nhận được một số lợi ích (tôi tối ưu hóa mạnh mẽ hơn tôi đoán). Tôi không thực sự chắc chắn chính xác những gì diễn ra trong các lần tối ưu hóa trình biên dịch khác nhau nhưng tôi có một chút kinh nghiệm với mã kết quả (tôi sử dụng gcc). Đơn giản chỉ cần đặt các phương thức riêng trong lớp gốc sẽ buộc chúng phải có liên kết bên ngoài.
Tôi có thể sai ở đây, nhưng cách tôi diễn giải đó là trình tối ưu hóa thời gian biên dịch không thể loại bỏ hàm ngay cả khi tất cả các trường hợp cuộc gọi của nó được đặt hoàn toàn bên trong TU đó. Vì một số lý do, ngay cả LTO cũng từ chối loại bỏ định nghĩa hàm ngay cả khi có vẻ như tất cả các trường hợp cuộc gọi trong toàn bộ nhị phân được liên kết đều được nội tuyến. Tôi tìm thấy một số tài liệu tham khảo nói rằng đó là vì trình liên kết không biết liệu bằng cách nào đó bạn vẫn gọi hàm bằng cách sử dụng các hàm con trỏ (mặc dù tôi không hiểu tại sao trình liên kết không thể hiểu rằng địa chỉ của phương thức đó không bao giờ được sử dụng ).
Đây không phải là trường hợp nếu bạn sử dụng đề xuất của tôi và đặt các phương thức riêng tư đó trong một pimpl bên trong một không gian tên ẩn danh. Nếu chúng được nội tuyến, các hàm sẽ KHÔNG xuất hiện trong (với -O3, bao gồm -finline-function) tệp đối tượng.
Theo cách tôi hiểu, trình tối ưu hóa, khi quyết định có hay không thực hiện một hàm, có tính đến tác động của nó đối với kích thước mã. Vì vậy, bằng cách sử dụng đề xuất của tôi, tôi sẽ làm cho nó tối ưu hơn một chút "rẻ hơn" để tối ưu hóa nội tuyến các phương thức riêng tư đó.
PCALL
là hành vi không xác định. Bạn không thể truyềnA
tới aPImpl
và sử dụng nó trừ khi đối tượng cơ bản thực sự thuộc loạiPImpl
. Tuy nhiên, trừ khi tôi nhầm, người dùng sẽ chỉ tạo các đối tượng thuộc loạiA
.