Ось спосіб використовувати клас проксі для доступу до елементів у масиві членів на ім'я. Це дуже C ++ і не має користі проти функцій аксесуарів, що повертаються назад, за винятком синтаксичних уподобань. Це перевантажує ->
оператора для доступу до елементів як членів, тому, щоб бути прийнятним, потрібно як не подобатися синтаксису аксесуарів ( d.a() = 5;
), так і терпіти використання ->
з об'єктом, що не вказує. Я думаю, це також може заплутати читачів, не знайомих з кодом, тому це може бути скоріше акуратним трюком, ніж чимось, що ви хочете поставити у виробництво.
Data
Структура в цьому коді також включає в себе перевантаження для оператора індексу для доступу індексуються елементів всередині його ar
елемента масиву, а також begin
і end
функцій, для ітерації. Крім того, все це перевантажено версіями non-const та const, які, на мою думку, потрібно було включити для повноти.
Коли Data
's ->
використовується для доступу до елемента по імені (наприклад:) my_data->b = 5;
, Proxy
об'єкт повертається. Потім, оскільки це Proxy
значення не є покажчиком, його власний ->
оператор викликається автоматичним ланцюгом, який повертає вказівник до себе. Таким чином, Proxy
об'єкт інстанціюється та залишається дійсним під час оцінки початкового виразу.
Структура Proxy
об'єкта заповнює його 3 опорні члени a
, b
і c
відповідно до вказівника, переданого в конструкторі, який, як передбачається, вказує на буфер, що містить щонайменше 3 значення, тип яких задано як шаблон параметра T
. Отже, замість використання названих посилань, які є членами Data
класу, це економить пам'ять, заповнюючи посилання в точці доступу (але, на жаль, використовуючи, ->
а не .
оператор).
Для того, щоб перевірити, наскільки оптимізатор компілятора усуває всі опосередковані дії, введені в результаті використання Proxy
, наведений нижче код містить 2 версії main()
. У #if 1
версії використовуються оператори ->
і []
, і #if 0
версія виконує еквівалентний набір процедур, але тільки шляхом прямого доступу Data::ar
.
Nci()
Функція генерує під час виконання цілочисельних значень для ініціалізації елементів масиву, який запобігає оптимізатор від тільки підключити постійні значення безпосередньо в кожен std::cout
<<
виклик.
Для gcc 6.2, використовуючи -O3, обидві версії main()
генерують однакову збірку (перемикання між #if 1
та #if 0
перед першим main()
для порівняння): https://godbolt.org/g/QqRWZb
#include <iostream>
#include <ctime>
template <typename T>
class Proxy {
public:
T &a, &b, &c;
Proxy(T* par) : a(par[0]), b(par[1]), c(par[2]) {}
Proxy* operator -> () { return this; }
};
struct Data {
int ar[3];
template <typename I> int& operator [] (I idx) { return ar[idx]; }
template <typename I> const int& operator [] (I idx) const { return ar[idx]; }
Proxy<int> operator -> () { return Proxy<int>(ar); }
Proxy<const int> operator -> () const { return Proxy<const int>(ar); }
int* begin() { return ar; }
const int* begin() const { return ar; }
int* end() { return ar + sizeof(ar)/sizeof(int); }
const int* end() const { return ar + sizeof(ar)/sizeof(int); }
};
// Nci returns an unpredictible int
inline int Nci() {
static auto t = std::time(nullptr) / 100 * 100;
return static_cast<int>(t++ % 1000);
}
#if 1
int main() {
Data d = {Nci(), Nci(), Nci()};
for(auto v : d) { std::cout << v << ' '; }
std::cout << "\n";
std::cout << d->b << "\n";
d->b = -5;
std::cout << d[1] << "\n";
std::cout << "\n";
const Data cd = {Nci(), Nci(), Nci()};
for(auto v : cd) { std::cout << v << ' '; }
std::cout << "\n";
std::cout << cd->c << "\n";
//cd->c = -5; // error: assignment of read-only location
std::cout << cd[2] << "\n";
}
#else
int main() {
Data d = {Nci(), Nci(), Nci()};
for(auto v : d.ar) { std::cout << v << ' '; }
std::cout << "\n";
std::cout << d.ar[1] << "\n";
d->b = -5;
std::cout << d.ar[1] << "\n";
std::cout << "\n";
const Data cd = {Nci(), Nci(), Nci()};
for(auto v : cd.ar) { std::cout << v << ' '; }
std::cout << "\n";
std::cout << cd.ar[2] << "\n";
//cd.ar[2] = -5;
std::cout << cd.ar[2] << "\n";
}
#endif