Чи можна уникнути шаблону?
Ні.
C ++ має дуже обмежені можливості генерації коду, автоматичне введення коду не є частиною їх.
Застереження: далі йдеться про глибоке занурення в проксі, із закликом перешкоджати користувачеві отримувати грубі лапи щодо функцій, які вони не повинні викликати, не минаючи проксі.
Чи можна ускладнити забуття про дзвінок до / після функції?
Примусове делегування через проксі-сервер ... дратує. Зокрема, функції не можуть бути public
або protected
, оскільки в іншому випадку абонент може отримати їх грубі руки, і ви можете оголосити про втрату.
Таким чином, одним з можливих рішень є оголошення всіх функцій приватними та надання проксі-серверів, які забезпечують ведення журналу. Абстрагуючись від цього, зробити цей масштаб для багатьох класів жахливим, хоча це одноразова вартість:
template <typename O, typename R, typename... Args>
class Applier {
public:
using Method = R (O::*)(Args...);
constexpr explicit Applier(Method m): mMethod(m) {}
R operator()(O& o, Args... args) const {
o.pre_call();
R result = (o.*mMethod)(std::forward<Args>(args)...);
o.post_call();
return result;
}
private:
Method mMethod;
};
template <typename O, typename... Args>
class Applier<O, void, Args...> {
public:
using Method = void (O::*)(Args...);
constexpr explicit Applier(Method m): mMethod(m) {}
void operator()(O& o, Args... args) const {
o.pre_call();
(o.*mMethod)(std::forward<Args>(args)...);
o.post_call();
}
private:
Method mMethod;
};
template <typename O, typename R, typename... Args>
class ConstApplier {
public:
using Method = R (O::*)(Args...) const;
constexpr explicit ConstApplier(Method m): mMethod(m) {}
R operator()(O const& o, Args... args) const {
o.pre_call();
R result = (o.*mMethod)(std::forward<Args>(args)...);
o.post_call();
return result;
}
private:
Method mMethod;
};
template <typename O, typename... Args>
class ConstApplier<O, void, Args...> {
public:
using Method = void (O::*)(Args...) const;
constexpr explicit ConstApplier(Method m): mMethod(m) {}
void operator()(O const& o, Args... args) const {
o.pre_call();
(o.*mMethod)(std::forward<Args>(args)...);
o.post_call();
}
private:
Method mMethod;
};
Примітка: Я не з нетерпінням додаю підтримку volatile
, але ніхто не використовує її, так?
Коли ця перша перешкода пройдена, ви можете використовувати:
class MyClass {
public:
static const Applier<MyClass, void> a;
static const ConstApplier<MyClass, int, int> b;
void pre_call() const {
std::cout << "before\n";
}
void post_call() const {
std::cout << "after\n";
}
private:
void a_impl() {
std::cout << "a_impl\n";
}
int b_impl(int x) const {
return mMember * x;
}
int mMember = 42;
};
const Applier<MyClass, void> MyClass::a{&MyClass::a_impl};
const ConstApplier<MyClass, int, int> MyClass::b{&MyClass::b_impl};
Це цілком зразковий шаблон, але принаймні візерунок чіткий, і будь-яке порушення буде стирчати як хворий великий палець. Також легше застосовувати пост-функції таким чином, а не відстежувати кожнуreturn
.
Синтаксис для виклику теж не такий вже й чудовий:
MyClass c;
MyClass::a(c);
std::cout << MyClass::b(c, 2) << "\n";
Має бути можливо зробити краще ...
Зверніть увагу, що в ідеалі ви хотіли б:
- використовувати член даних
- чий тип кодує зміщення до класу (безпечно)
- чий тип кодує метод для виклику
Рішення на півдорозі є (на півдорозі, оскільки небезпечно ...):
template <typename O, size_t N, typename M, M Method>
class Applier;
template <typename O, size_t N, typename R, typename... Args, R (O::*Method)(Args...)>
class Applier<O, N, R (O::*)(Args...), Method> {
public:
R operator()(Args... args) {
O& o = *reinterpret_cast<O*>(reinterpret_cast<char*>(this) - N);
o.pre_call();
R result = (o.*Method)(std::forward<Args>(args)...);
o.post_call();
return result;
}
};
template <typename O, size_t N, typename... Args, void (O::*Method)(Args...)>
class Applier<O, N, void (O::*)(Args...), Method> {
public:
void operator()(Args... args) {
O& o = *reinterpret_cast<O*>(reinterpret_cast<char*>(this) - N);
o.pre_call();
(o.*Method)(std::forward<Args>(args)...);
o.post_call();
}
};
template <typename O, size_t N, typename R, typename... Args, R (O::*Method)(Args...) const>
class Applier<O, N, R (O::*)(Args...) const, Method> {
public:
R operator()(Args... args) const {
O const& o = *reinterpret_cast<O const*>(reinterpret_cast<char const*>(this) - N);
o.pre_call();
R result = (o.*Method)(std::forward<Args>(args)...);
o.post_call();
return result;
}
};
template <typename O, size_t N, typename... Args, void (O::*Method)(Args...) const>
class Applier<O, N, void (O::*)(Args...) const, Method> {
public:
void operator()(Args... args) const {
O const& o = *reinterpret_cast<O const*>(reinterpret_cast<char const*>(this) - N);
o.pre_call();
(o.*Method)(std::forward<Args>(args)...);
o.post_call();
}
};
Він додає по одному байту на "метод" (оскільки C ++ такий дивний), і вимагає деяких досить задіяних визначень:
class MyClassImpl {
friend class MyClass;
public:
void pre_call() const {
std::cout << "before\n";
}
void post_call() const {
std::cout << "after\n";
}
private:
void a_impl() {
std::cout << "a_impl\n";
}
int b_impl(int x) const {
return mMember * x;
}
int mMember = 42;
};
class MyClass: MyClassImpl {
public:
Applier<MyClassImpl, sizeof(MyClassImpl), void (MyClassImpl::*)(), &MyClassImpl::a_impl> a;
Applier<MyClassImpl, sizeof(MyClassImpl) + sizeof(a), int (MyClassImpl::*)(int) const, &MyClassImpl::b_impl> b;
};
Але принаймні використання є "природним":
int main() {
MyClass c;
c.a();
std::cout << c.b(2) << "\n";
return 0;
}
Особисто для забезпечення цього я просто використав би:
class MyClass {
public:
void a() { log(); mImpl.a(); }
int b(int i) const { log(); return mImpl.b(i); }
private:
struct Impl {
public:
void a_impl() {
std::cout << "a_impl\n";
}
int b_impl(int x) const {
return mMember * x;
}
private:
int mMember = 42;
} mImpl;
};
Не зовсім надзвичайно, але просто ізоляція держави MyClass::Impl
утрудняє реалізацію логіки MyClass
, що, як правило, достатньо для того, щоб супровідники слідували зразку.