如何确保类的每个方法都首先调用其他方法
How to ensure that every method of a class calls some other method first?
我有:
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++的代码生成能力非常有限,但自动注入代码不是其中的一部分。
免责声明:以下是对代理的深入探讨,其调用是防止用户在不绕过代理的情况下将他们肮脏的爪子放在他们不应该调用的函数上。
是否有可能使忘记调用前/后功能更难?
通过代理强制委派是...烦人。具体来说,这些函数不可能是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 << "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
中实现逻辑,这通常足以确保维护者遵循模式。
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- GlobalAlloc而不是其他分配方法
- 让bool方法返回其他整数
- 还有其他方法可以在数组中写入多维数组吗?
- 如何使用 C/C++ 和 system() 系统调用以外的其他方法在 Linux 中获取文件功能?
- Unity3d 中还有其他方法可以访问设备相机吗?
- 从 int 中剥离位时,编译器会警告一个转换,但不警告其他转换.有解决方法吗?
- Sizeof返回的是指针大小,而不是数组大小.有其他方法可以找到尺寸吗
- 继承构造函数和其他变量的解决方法
- 还有其他方法可以为乘法表编写循环以获取运行时值吗?
- 在另一个 QThread 上运行成员方法时,无法将事件发送到其他线程拥有的对象
- 如何使 extern 方法在共享库中定义,但使用 cmake 在其他目标中声明?
- isdigit() 和 isalnum() 给出错误,因为输入是一个常量字符并且无法转换。其他可能查看输入是否为数字的方法?
- 是否有其他方法将.dll文件从一个项目复制到我的启动项目中的可执行文件旁边
- 将指针类方法作为参数传递给其他类方法C
- 如何为其他类成员函数编写模板包装方法
- std::d eclare_if 或其他在编译时丢弃成员声明的假设方法
- 当从其他方法返回 vector 时,C++无法访问矢量元素
- 我不允许更改变量的声明,我可以编辑哪些其他方法
- 如何使用Python脚本或其他一些方法来美化所有文件