Ist es möglich, die Kesselplatte zu vermeiden?
Nein.
C ++ verfügt nur über sehr eingeschränkte Möglichkeiten zur Codegenerierung. Das automatische Einfügen von Code gehört nicht dazu.
Haftungsausschluss: Das Folgende ist ein tiefer Einblick in das Proxying mit dem Aufruf, zu verhindern, dass der Benutzer seine schmutzigen Pfoten auf die Funktionen bekommt, die er nicht aufrufen sollte, ohne den Proxy zu umgehen.
Ist es möglich, das Vergessen, Pre- / Post-Funktion aufzurufen, schwieriger zu machen?
Das Erzwingen der Delegierung über einen Proxy ist ... ärgerlich. Insbesondere sind die Funktionen nicht möglich sein publicoder werden protected, da sonst der Anrufer seine schmutzigen Hände auf sie bekommen können , und Sie können verwirkt erklären.
Eine mögliche Lösung besteht daher darin, alle Funktionen als privat zu deklarieren und Proxys bereitzustellen, die die Protokollierung erzwingen. Abstrahiert, um diese Skala über mehrere Klassen hinweg zu erstellen, ist schrecklich kesselartig, obwohl es sich um einmalige Kosten handelt:
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;
};
Hinweis: Ich freue mich nicht darauf, Unterstützung für hinzuzufügen volatile, aber niemand nutzt sie, oder?
Sobald diese erste Hürde überwunden ist, können Sie Folgendes verwenden:
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};
Es ist ziemlich das Kesselschild, aber zumindest ist das Muster klar, und jede Verletzung wird wie ein schmerzender Daumen herausragen. Es ist auch einfacher, Post-Funktionen auf diese Weise anzuwenden, als jede einzelne zu verfolgenreturn .
Die aufzurufende Syntax ist auch nicht gerade so gut:
MyClass c;
MyClass::a(c);
std::cout << MyClass::b(c, 2) << "\n";
Es sollte möglich sein, es besser zu machen ...
Beachten Sie, dass Sie im Idealfall:
- Verwenden Sie ein Datenelement
- deren Typ den Offset zur Klasse codiert (sicher)
- deren Typ die aufzurufende Methode codiert
Eine Lösung auf halbem Weg ist (auf halbem Weg, weil unsicher ...):
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();
}
};
Es fügt ein Byte pro "Methode" hinzu (weil C ++ so seltsam ist) und erfordert einige ziemlich komplizierte Definitionen:
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;
};
Aber zumindest ist die Verwendung "natürlich":
int main() {
MyClass c;
c.a();
std::cout << c.b(2) << "\n";
return 0;
}
Um dies persönlich durchzusetzen, würde ich einfach Folgendes verwenden:
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;
};
Nicht gerade außergewöhnlich, aber das einfache Isolieren des Zustands in MyClass::Implerschwert die Implementierung von Logik MyClass, was im Allgemeinen ausreicht, um sicherzustellen, dass die Betreuer dem Muster folgen.