C ++ 11避免“Call Super”代码闻到

时间:2017-02-04 05:09:35

标签: c++ c++11 lambda

我正在寻找避免“召唤超级”代码气味的方法。当重新实现该函数时需要子类来调用超类的虚函数版本时,会出现此代码异味。

class Base
{
    public:
        virtual void foo(){ ... }
}
class Derived : public Base
{
    public:
        virtual void foo(){ Base::foo();// required! ... }
}

如果继承只有一层深,我可以使用模板方法

class Base
{
    public:
        void foo(){ ... ; foo_impl(); }
    protected:
        virtual void foo_impl(){}
}
class Derived : public Base
{
    protected:
        virtual void foo_impl(){ ... }
}

但如果我需要继承Derived,我会回到我开始的地方。

我正在考虑注册方法。

class Base
{
    public:
        Base()
        {
            _registerCallback( [this](){ _baseFoo(); } );
        }
        void foo()
        {
            for( auto f : _callbacks )
                f();
        }
    protected:
        void registerCallback( std::function<void()> f )
        { 
            _callbacks << f;
        }
    private:
        void _baseFoo() { ... }
        std::list< std::function<void()> > _callbacks;
}
class Derived : public Base
{
    public:
        Derived()
        {
            _registerCallback( [this](){ _derivedFoo(); } );
        }
    private:
        virtual void _derivedFoo(){ ... }
}

有更标准的方法吗?这种方法有任何问题或改进吗?

5 个答案:

答案 0 :(得分:5)

使用

sizeof(a)->b

是IMO的最佳方法。我不确定你为什么会考虑#34;代码闻到&#34;。

  1. 在您建议的最后一种方法中,错误的可能性更高。
  2. 更容易发现对class Derived : public Base { public: virtual void foo(){ Base::foo();// required! ... } } 的未接来电。
  3. 如果从Base::foo()派生的所有类别都需要实现Base所做的,那么公共代码在Base::foo()中的效果会更好。派生类只需要进行调用。
  4. 对于它的价值,我们在我的工作中使用了这种模式,并且已经证明它已经使用了20多年。

答案 1 :(得分:1)

如果在每个级别引入新的虚拟成员函数并在下一个级别覆盖它,则可以继续使用模板方法:

template <typename> struct tag {};

class Base
{
    public:
        void foo() { ... ; foo_impl(tag<Base>{}); }
    protected:
        virtual void foo_impl(tag<Base>) {}
};

class Derived1 : public Base
{
    protected:
        virtual void foo_impl(tag<Base>) override final { ... ; foo_impl(tag<Derived1>{}); }
        virtual void foo_impl(tag<Derived1>) {}
};

class Derived2 : public Derived1
{
    protected:
        virtual void foo_impl(tag<Derived1>) override final { ... ; foo_impl(tag<Derived2>{}); }
        virtual void foo_impl(tag<Derived2>) {}
};

class Derived3 : public Derived2
{
    protected:
        virtual void foo_impl(tag<Derived2>) override final { ... ; foo_impl(tag<Derived3>{}); }
        virtual void foo_impl(tag<Derived3>) {}
};

如果你不喜欢标签发送,你可以给方法改为不同的名字,或许类似于foo_impl_N

答案 2 :(得分:0)

我认为所有这些过度工程。

chris mentioned主要关注的是孩子们没有打电话给他们父母的相应成员函数,这给出了修复该部分的想法:

#include <cassert>

class Base {
public:
    void foo() {
        foo_impl();
        assert(base_foo_called && "call base class foo_impl");
    }

protected:
    virtual void foo_impl() { base_foo_called = true; }

private:
    bool base_foo_called = false;
};

class DerivedFine : public Base {
protected:
    void foo_impl() override {
        Base::foo_impl();
    }
};

class DerivedDerivedFine : public DerivedFine {
protected:
    void foo_impl() override {
        DerivedFine::foo_impl();
    }
};

class DerivedDerivedNotFine : public DerivedFine {
protected:
    void foo_impl() override {}
};

int main() {
    DerivedFine foo;
    foo.foo();

    DerivedDerivedFine bar;
    bar.foo();

    DerivedDerivedNotFine baz;
    baz.foo(); // this asserts
}

答案 3 :(得分:0)

这是一个受this answer启发的想法

这个想法是利用struct /类的构造函数和析构函数提供一种被继承的“前/后函数调用”机制的事实。因此,我们可以使用函子并在构造函数/析构函数中定义pre / post函数调用,而不是在虚拟方法本身中执行pre / post函数调用。这样,从基函子继承的函子将继承pre / post函数调用。

代码

struct BasePrePostFunctor
{
    BasePrePostFunctor()
    {
        printf("Base pre-func\n");
    }
    virtual void operator()()
    {
        printf("Base Main func\n");
    }
    ~BasePrePostFunctor()
    {
        printf("Base post-func\n");
    }
};

struct DerivedPrePostFunctor : BasePrePostFunctor
{
    DerivedPrePostFunctor()
    {
        printf("Derived pre-func\n");
    }
    void operator()() override
    {
        printf("Derived main func\n");
    }
    ~DerivedPrePostFunctor()
    {
        printf("Derived post-func\n");
    }
};

class BaseClass
{
    public:
        virtual void virtual_func()
        {
            BasePrePostFunctor func;
            func();
        }
};

class DerivedClass : public BaseClass
{
    public:
        void virtual_func() override
        {
            DerivedPrePostFunctor func;
            func();
        }
};

int main(int argc, char** argv)
{
    DerivedClass derived;
    derived.virtual_func();
};

输出

Base pre-func
Derived pre-func
Derived main func
Derived post-func
Base post-func

答案 4 :(得分:-1)

CRTP可以解决所有问题。

对于每个foo方法,您实现一个空的非虚拟foo_before(),它在您的CRTP帮助程序中不执行任何操作。

CRTP助手采用派生和基础。其virtual void foo()调用static_cast<Derived*>(this)->foo_before()然后调用Base::foo()然后调用after_foo()

struct Base {
  virtual void foo() { std::cout << "foo\n"; }
  virtual ~Base() {};
};

template<class D, class B=Base>
struct foo_helper:B {
  virtual void foo() {
    static_cast<D*>(this)->before_foo();
    this->B::foo();
    static_cast<D*>(this)->after_foo();
  }
private:
  void before_foo() {}; void after_foo() {};
};

struct Derived1 : foo_helper<Derived1> {
  void before_foo() { std::cout << "before1\n"; }
};

struct Derived2 : foo_helper<Derived2> {
  void before_foo() { std::cout << "before2\n"; }
  void after_foo() { std::cout << "after2\n"; }
};

struct DoubleDerived : foo_helper<DoubleDerived, Derived2> {
  void after_foo() { std::cout << "even more after\n"; }
};

int main() {
    std::cout << "---- Derived1\n";
    Derived1 d1;
    d1.foo();
    std::cout << "---- Derived2\n";
    Derived2 d2;
    d2.foo();
    std::cout << "---- DoubleDerived\n";
    DoubleDerived dd;
    dd.foo();
}

Live example

输出:

---- Derived1
before1
foo
---- Derived2
before2
foo
after2
---- DoubleDerived
before2
foo
after2
even more after