【问题标题】:How to verify a function doesn't call itself?如何验证函数不调用自身?
【发布时间】:2017-11-15 08:11:02
【问题描述】:

这是我的代码,在这里你可以看到带有接口函数Do()Base 类型的对象,我总是使用Base 类型的对象(模板具有更多类型)并且总是调用Do() 接口。因此,对于ImplBaseDummy1ImplBaseDummy2,我可以得到f()ImplBaseDummy2 的具体实现或Base 的默认实现ImplBaseDummy1。到目前为止,这一切都很好,我有很多代码可以按预期工作,我不想更改 API,但如果你能说服我应该这样做,我可能会这样做。最近我不得不添加更多类型,比如ImplBase 我尽量不要重复代码,所以我创建了一个类似的结构,希望它能够继续工作。问题是如何在定义时强制调用f 的特定实现,并在缺少特定实现时使用默认实现(来自Base),所有这些都应该在为@987654336 类型的对象调用接口函数Do() 时发生@(以某种方式模板化)。

#include <iostream>

template <class Impl>
class Base
{
public:
 void Do() { f_impl(); }
 void f() {std::cout << "Base::f" << std::endl; }
protected:
 void f_impl() { static_cast<Impl*>(this)->f(); }
};

class ImplBaseDummy1 : public Base<ImplBaseDummy1>
{
};

class ImplBaseDummy2 : public Base<ImplBaseDummy2>
{
public:
 void f() {std::cout << "ImplBaseDummy2::f" << std::endl; }
};

template <class Actual>
class ImplBase : public Base<ImplBase<Actual>>
{
public:
 typedef Base<ImplBase<Actual>> Parent;
 void f() { static_cast<Actual*>(this)->f(); }
};

class Derived1 : public ImplBase<Derived1>
{
public:
 typedef ImplBase<Derived1> Parent;
 void f() {std::cout << "Derived1::f" << std::endl; }
};

class Derived2 : public ImplBase<Derived2>
{
public:
 typedef ImplBase<Derived2> Parent;
 using Parent::Parent::f;
};

int main()
{
  Base<ImplBaseDummy1> d01;
  d01.Do();
  Base<ImplBaseDummy2> d02;
  d02.Do();
  Base<ImplBase<Derived1>> d1;
  d1.Do();
  Base<ImplBase<Derived2>> d2;
  d2.Do();
  return 0;
}

此代码按预期编译和运行。我的问题是关于我想实现的安全机制,以防我忘记在Derived2 中写using Parent::Parent::f;(这是最初的意图,如果缺少特定实现,请从Base 调用默认实现)。在这种情况下,ImplBase&lt;Derived2&gt;::f() 会无限期地调用自身,直到它崩溃(或者在编译 -O3 优化级别时永远不会退出)。我想实现它类似于这个

void f() { static_assert(&f != &Actual::f, "function calls itself"); static_cast<Actual*>(this)->f(); }
void f() { static_assert(!std::is_same<decltype(f), decltype(Actual::f)>::value, "function calls itself"); static_cast<Actual*>(this)->f(); }

但是两个实现都没有编译,还有其他想法如何实现这个检查吗?需要强调的是,运行时解决方案不是我要寻找的,我最好不要进行任何检查,并且在遇到问题时打破我的头,而不是对Do 接口的任何调用执行额外的测试。

【问题讨论】:

  • 您可以使用PRETTY_FUNCTION 查看调用了哪个函数以及何时调用
  • 在所有情况下,您都无法检测到间接递归(例如,您的函数调用 foo,而 bar 又调用您的函数)。
  • @Bl4ckb0ne,如果我可以在编译时比较来自 ImplBase::f 的 PRETTY_FUNCTION 和 Actual::f 的 PRETTY_FUNCTION,那就太好了,但我认为这做不到。
  • @BasileStarynkevitch,我想谈谈非常具体的情况,而不是一般情况
  • 您愿意花几个月的时间来编写您的验证程序?

标签: c++ linux c++11 gcc crtp


【解决方案1】:

一般来说,在编译时无法证明可重入。

但可以在运行时检查:

static bool inside = false;
assert(!inside);
inside = true;

// rest of function

inside = false;
return whatever; // if non void

局部变量的析构函数在技术上仍然可以最终调用函数。这可以通过(可重复使用的)RAII 样式对象部分解决:

class nonreentrant
{
    bool& inside;
    public:
    nonreentrant(bool& inside): inside(inside)
    {
        assert(!this->inside);
        this->inside = true;
    }
    ~nonreentrant()
    {
        this->inside = false;
    }
};

// usage
static bool inside = false;
nonreentrant guard(inside);

// rest of function

不幸的是,这不适用于参数,其析构函数将在之后运行。

从技术上讲,一些涉及返回值的移动构造函数最终可能会调用该函数。 void 函数和那些返回平凡可移动对象的函数应该是万无一失的。

【讨论】:

  • 运行时解决方案在这里不起作用,我需要在编译时验证我的代码,或者冒险不使用任何验证。我知道通常不可能在编译时证明重新进入,但我不是在谈论一般情况,这是我希望验证的非常具体的实现。事实是static_cast&lt;Actual*&gt;(this)-&gt;f() 解析为正确的东西(在编译时),所以我不明白为什么我无法在编译时验证任何关于此解析的内容
【解决方案2】:

您的代码执行未定义的行为,因此无法“检查它是否正常工作”。该标准对程序的行为提出零要求;所有行为都是“正确的”。

ImplBase&lt;Derived2&gt; 不是Derived2,因此static_cast&lt;Derived2*&gt;(this)-&gt;f() 会导致未定义的行为。这里的无限循环与您想要发生的一样正确。

我已经解决了类似的问题,但没有做未定义的行为。

template <class Impl>
class Base {
public:
  void f_impl() {std::cout << "Base::f" << std::endl; }
};
template <class Actual>
class ImplBase : public Base<ImplBase<Actual>> {
public:
  void f() { static_cast<Actual*>(this)->f_impl(); }
};

class Derived1 : public ImplBase<Derived1> {
public:
  void f_impl() {std::cout << "Derived1::f" << std::endl; }
};

class Derived2 : public ImplBase<Derived2> {
public:
};

现在,Derived1 d1; d1.f();Derived2 d2; d2.f(); 做我认为你想要的; d1 已覆盖 f,而 d2 使用 Base 行为。

我们将接口与实现分开,允许实现分派独立于接口分派而存在。

【讨论】:

  • 我不喜欢改变界面,但如果我做错了,我可能会这样做。如果您不介意,我会进一步更新我正在使用的代码以反映它的作用,如果您有其他建议,请告诉我Base 具有Do() 功能作为我正在使用的接口调用f() 所以我不得不使用这个名字
  • @e27 您的代码仍有未定义的行为。您不能将指针强制转换为不与它相交的类型,句号。
【解决方案3】:

不要使用 Parent::Parent::f 声明。 delcare 显式调用 parent::Parent::f() 的 f()。那会解决你的问题。

如果您想要额外的保护以防止忘记声明,您应该使用受保护的继承声明您的最终类,如下所示:

class Derived1 : protected ImplBase<Derived1>
{
public:
 typedef ImplBase<Derived1> Parent;
 void f() {std::cout << "Derived1::f" << std::endl; }
};

class Derived2 : protected ImplBase<Derived2>
{
public:
 typedef ImplBase<Derived2> Parent;
 // void f() { Parent::Parent::f(); }  // uncomment to avoid compile error.
};

生成的错误消息非常清楚,并按名称提到了错误的类。

这段代码相当做作。我很难弄清楚这种继承的用途是什么,因为直接直接声明 Derived1 或 Derived2 对象类型的对象就可以很好地工作,而无需在原始代码中使用 ImplBase::f()。

这与元编程有关吗?我们很感兴趣。

这里是完整的解决方案,修改了 4 行代码。

#include <iostream>

template <class Impl>
class Base
{
public:
    void Do() { f_impl(); }
    void f() { std::cout << "Base::f" << std::endl; }
protected:
    void f_impl() { static_cast<Impl*>(this)->f(); }
};

class ImplBaseDummy1 : public Base<ImplBaseDummy1>
{
};

class ImplBaseDummy2 : public Base<ImplBaseDummy2>
{
public:
    static void f() { std::cout << "ImplBaseDummy2::f" << std::endl; }
};

template <class Actual>
class ImplBase : public Base<ImplBase<Actual>>
{
public:
    typedef Base<ImplBase<Actual>> Parent;
    void f() { static_cast<Actual*>(this)->f(); }
};

class Derived1 : protected ImplBase<Derived1>
{
public:
    typedef ImplBase<Derived1> Parent;
    void f() { std::cout << "Derived1::f" << std::endl; }
};

class Derived2 : protected ImplBase<Derived2>
{
public:
    typedef ImplBase<Derived2> Parent;
    //using Parent::Parent::f;
    //void f() { Parent::Parent::f(); } // uncomment to remove compile error
};

int main()
{
    Base<ImplBaseDummy1> d01;
    d01.Do();
    Base<ImplBaseDummy2> d02;
    d02.Do();
    Base<ImplBase<Derived1>> d1;
    d1.Do();
    Base<ImplBase<Derived2>> d2;
    d2.Do();
    return 0;
}

【讨论】:

  • 问题是我会忘记实现fin Derived2 我需要一些东西来提醒我这一点(编译错误会很棒)
  • 我明白了...如果您或下一个开发人员忘记定义 f(),使 ImplBase::f() 受保护会产生您想要的错误。
  • @e271p314 这是用于什么编译器的?
  • 我使用的是 gcc 5.3.1,请注意我必须保持 `ImplBase::f()` 公开,因为它是从 Base 使用 Do 接口调用的
  • 上述建议也适用于 Dummy 类,无论哪种方式。因为 Dummy 系列使用不同的层次结构。
猜你喜欢
  • 2022-01-25
  • 1970-01-01
  • 1970-01-01
  • 2019-07-02
  • 2013-11-19
  • 2020-09-22
  • 1970-01-01
  • 2016-08-04
  • 1970-01-01
相关资源
最近更新 更多