如何确保类的每个方法都首先调用其他方法

How to ensure that every method of a class calls some other method first?

本文关键字:方法 其他 调用 何确保 确保      更新时间:2023-10-16

我有:

class Foo {
public:
void log() { }
void a() {
log();
}
void b() {
log();
}
};

有没有办法让我拥有每个Foo方法,调用log(),但不必显式键入 log() 作为每个函数的第一行? 我想这样做,这样我就可以为每个函数添加行为,而不必遍历每个函数并确保调用完成,并且当我添加新函数时,代码会自动添加......

这可能吗?我无法想象如何使用宏来做到这一点,所以不知道从哪里开始......到目前为止,我想到的唯一方法是添加一个"预构建步骤",以便在编译之前扫描文件并编辑源代码,但这似乎不是很聪明......

编辑:只是为了澄清 - 我不希望 log() 明显地调用自己。它不需要是类的一部分。

编辑:我更喜欢使用跨平台工作的方法,并且只使用stl。

由于operator ->的不寻常属性,我们可以在任何成员访问之前注入代码,但代价是语法略微弯曲:

// Nothing special in Foo
struct Foo {
void a() { }
void b() { }
void c() { }
};
struct LoggingFoo : private Foo {
void log() const { }
// Here comes the trick
Foo const *operator -> () const { log(); return this; }
Foo       *operator -> ()       { log(); return this; }
};

用法如下所示:

LoggingFoo f;
f->a();

在科里鲁现场观看

这是包装器问题的最小(但非常通用)的解决方案:

#include <iostream>
#include <memory>
template<typename T, typename C>
class CallProxy {
T* p;
C c{};
public:
CallProxy(T* p) : p{p} {}
T* operator->() { return p; } 
};
template<typename T, typename C>
class Wrapper {
std::unique_ptr<T> p;
public:
template<typename... Args>
Wrapper(Args&&... args) : p{std::make_unique<T>(std::forward<Args>(args)...)} {}
CallProxy<T, C> operator->() { return CallProxy<T, C>{p.get()}; } 
};
struct PrefixSuffix {
PrefixSuffix() { std::cout << "prefixn"; }
~PrefixSuffix() { std::cout << "suffixn"; }
};
struct MyClass {
void foo() { std::cout << "foon"; }
};

int main()
{
Wrapper<MyClass, PrefixSuffix> w;
w->foo();
}

定义一个PrefixSuffix类,前缀代码在其构造函数中,后缀代码在析构函数中是要走的路。然后,您可以使用Wrapper类(使用->访问原始类的成员函数),并且将为每次调用执行前缀和后缀代码。

现场观看。

感谢这篇论文,我在那里找到了解决方案。


作为旁注:如果必须包装的class没有virtual函数,则可以将Wrapper::p成员变量声明为普通对象,而不是指针,然后稍微破解Wrapper箭头运算符的语义;结果是您将不再有动态内存分配的开销。

你可以做一个包装器,比如

class Foo {
public:
void a() { /*...*/ }
void b() { /*...*/ }
};
class LogFoo
{
public:
template <typename ... Ts>
LogFoo(Ts&&... args) : foo(std::forward<Ts>(args)...) {}
const Foo* operator ->() const { log(); return &foo;}
Foo* operator ->() { log(); return &foo;}
private:
void log() const {/*...*/}
private:
Foo foo;
};

然后使用->而不是.

LogFoo foo{/* args...*/};
foo->a();
foo->b();

使用lambda 表达式和高函数来避免重复并最大限度地减少忘记调用log的机会:

class Foo
{
private:
void log(const std::string&)
{
}
template <typename TF, typename... TArgs>
void log_and_do(TF&& f, TArgs&&... xs)
{
log(std::forward<TArgs>(xs)...);
std::forward<TF>(f)();
}
public:
void a()
{
log_and_do([this]
{
// `a` implementation...
}, "Foo::a");
}
void b()
{
log_and_do([this]
{
// `b` implementation...
}, "Foo::b");
}
};

此方法的好处是,如果您决定更改日志记录行为,则可以更改log_and_do而不是更改调用log的每个函数。您还可以将任意数量的额外参数传递给log。最后,编译器应该对其进行优化 - 它的行为就像您在每种方法中手动编写了对log的调用一样。


您可以使用宏(叹息)来避免一些样板:

#define LOG_METHOD(...) 
__VA_ARGS__ 
{ 
log_and_do([&]
#define LOG_METHOD_END(...) 
, __VA_ARGS__); 
}

用法:

class Foo
{
private:
void log(const std::string&)
{
}
template <typename TF, typename... TArgs>
void log_and_do(TF&& f, TArgs&&... xs)
{
log(std::forward<TArgs>(xs)...);
std::forward<TF>(f)();
}
public:
LOG_METHOD(void a())
{
// `a` implementation...
}
LOG_METHOD_END("Foo::a");
LOG_METHOD(void b())
{
// `b` implementation...
}
LOG_METHOD_END("Foo::b");
};

我同意您原始帖子的评论中所写的内容,但是如果您确实需要这样做并且不喜欢使用 C 宏,则可以添加一个方法来调用您的方法。

下面是使用 C++ 2011 处理正确变化的函数参数的完整示例。使用 GCC 和 clang 进行测试

#include <iostream>
class Foo
{
void log() {}
public:
template <typename R, typename... TArgs>        
R call(R (Foo::*f)(TArgs...), const TArgs... args) {
this->log();
return (this->*f)(args...);
}
void a() { std::cerr << "A!n"; }
void b(int i) { std::cerr << "B:" << i << "n"; }
int c(const char *c, int i ) { std::cerr << "C:" << c << '/' << i << "n"; return 0; }
};
int main() {
Foo c;
c.call(&Foo::a);
c.call(&Foo::b, 1);
return c.call(&Foo::c, "Hello", 2);
}

是否有可能避免样板?

不。

C++的代码生成能力非常有限,但自动注入代码不是其中的一部分。


免责声明:以下是对代理的深入探讨,其调用是防止用户在不绕过代理的情况下将他们肮脏的爪子放在他们不应该调用的函数上。

是否有可能使忘记调用前/后功能更难?

通过代理强制委派是...烦人。具体来说,这些函数不可能publicprotected的,否则调用者可能会得到它肮脏的手,你可以宣布没收。

因此,一种可能的解决方案是将所有函数声明为私有,并提供强制执行日志记录的代理。抽象出这一点,使这种跨多个类的规模,是非常糟糕的样板,尽管这是一次性成本:

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 << "beforen";
}
void post_call() const {
std::cout << "aftern";
}
private:
void a_impl() {
std::cout << "a_impln";
}
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 << "beforen";
}
void post_call() const {
std::cout << "aftern";
}
private:
void a_impl() {
std::cout << "a_impln";
}
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_impln";
}
int b_impl(int x) const {
return mMember * x;
}
private:
int mMember = 42;
} mImpl;
};

不完全是特别的,但是简单地隔离MyClass::Impl中的状态使得很难在MyClass中实现逻辑,这通常足以确保维护者遵循模式。

相关文章: