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

时间:2017-02-03 08:34:48

标签: c++ c++11

我有:

class Foo {
   public:
      void log() { }

      void a() {
         log();
      }

      void b() {
         log();
      }
};

我是否可以使用Foo的每个方法调用log(),但是我不必显式键入log()作为每个函数的第一行? 我想这样做,这样我就可以为每个函数添加行为,而无需通过每个函数并确保调用,以及当我添加新函数时,代码会自动添加...

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

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

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

6 个答案:

答案 0 :(得分:109)

由于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();

See it live on Coliru

答案 1 :(得分:36)

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

#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 << "prefix\n"; }
    ~PrefixSuffix() { std::cout << "suffix\n"; }
};

struct MyClass {
    void foo() { std::cout << "foo\n"; }
};


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

定义PrefixSuffix类,其构造函数中包含前缀代码,析构函数中的后缀代码是可行的方法。然后,您可以使用Wrapper类(使用->访问原始类的成员函数),并且每次调用都将执行前缀和后缀代码。

live

this paper的信用,我找到了解决方案。

作为旁注:如果必须包装的class没有virtual个函数,可以将Wrapper::p成员变量声明为指针,而不是< em>普通对象,然后在Wrapper箭头操作符的语义上进行一些攻击;结果是你不再需要动态内存分配的开销。

答案 2 :(得分:17)

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

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();

答案 3 :(得分:9)

使用 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");
};

答案 4 :(得分:9)

我同意原始帖子的评论上写的内容,但是如果你真的需要这样做并且你不喜欢使用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);
}

答案 5 :(得分:5)

  

是否可以避免样板?

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 << "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中的逻辑很难实现,这通常足以确保维护者遵循这种模式。