Development Tip

클래스의 모든 메서드가 먼저 다른 메서드를 호출하는지 확인하는 방법은 무엇입니까?

yourdevel 2020. 10. 20. 08:16
반응형

클래스의 모든 메서드가 먼저 다른 메서드를 호출하는지 확인하는 방법은 무엇입니까?


나는 가지고있다 :

class Foo {
   public:
      void log() { }

      void a() {
         log();
      }

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

Foo, call 의 각 메서드를 가질 수 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();

Coliru에서 라이브보기


이것은 래퍼 문제에 대한 최소한의 (그러나 꽤 일반적인) 해결책입니다 .

#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클래스 를 사용할 수 있으며 ( ->원래 클래스의 멤버 함수에 액세스하기 위해 사용 ) 접두사 및 접미사 코드가 모든 호출에 대해 실행됩니다.

실시간으로 확인하세요 .

솔루션을 찾은 이 문서에 대한 크레딧 입니다.


참고로 : 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();

용도 람다 식고차 피하기 반복하는 기능을하고 전화를 잊어의 가능성을 최소화 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 << "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};

그것은 꽤 상용구이지만 적어도 패턴은 분명하며 어떤 위반도 엄지 손가락처럼 튀어 나올 것입니다. 또한 모든 .NET Framework를 추적하는 것보다 이러한 방식으로 사후 함수를 적용하는 것이 더 쉽습니다 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();
    }
};

이것은 "메서드"당 1 바이트를 추가합니다 (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;
}

Personally, to enforce this I would simply use:

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;
};

Not exactly extraordinary, but simply isolating the state in MyClass::Impl makes difficult to implement logic in MyClass, which is generally sufficient to ensure that maintainers follow the pattern.

참고URL : https://stackoverflow.com/questions/42019939/how-to-ensure-that-every-method-of-a-class-calls-some-other-method-first

반응형