【问题标题】:Ensuring a call method at compile time确保在编译时调用方法
【发布时间】:2014-02-17 17:30:27
【问题描述】:

如何确保在编译时调用特定方法?

例如,假设我有一个具有 2 个方法的对象:

struct Foo
{
   ... func1(...);
   ... func2(...);
};

并且我想确保在调用 func2 之前调用 func1,即:

int main()
{
   Foo f;
   ...
   f.func1(...);
   f.func2(...);
   f.func2(...); // and so on
}

但如果我这样做,我想生成一个编译错误:

int main()
{
   Foo f;
   ...
   f.func2(...);  // generate a compile error due the fact that func1 must be called first
   f.func1(...);
   f.func2(...); // and so on
}

【问题讨论】:

  • 您可以尝试使用constexpr,但在这种情况下将不起作用。 (在任何情况下都不能保证真的有效)。
  • 我猜这只能通过代码分析器来完成。编译器本身只能警告您未使用的函数 - AFAIK。
  • 如果您需要这样做,您可能应该继续考虑更好的类设计。这是一个顺序耦合反模式:en.wikipedia.org/wiki/Sequential_coupling

标签: c++ c++11


【解决方案1】:

虽然很好奇您为什么要这样做,但一般注意是您必须向用户展示一个不能错误使用的界面。胆量变得私密:

struct Foo
{
public:
    void callme()
    {
        func1();
        func2();
    }

private:
    ... func1(...);
    ... func2(...);


};

int main()
{
    Foo f;
    f.callme();
}

如果你需要强制一次性初始化对象,在构造函数中进行:

struct Foo
{
public:
    Foo()
    {
        func1();
    }

    func2(...);
private:
    ... func1(...);
};

int main()
{
    Foo f; // func1() called automagically
    f.func2();
}

设计类接口,你必须时刻考虑最糟糕的事情:用户从不阅读文档,用户总是忘记调用foo.initialize(),用户总是忘记释放内存并泄漏等等。

【讨论】:

  • 我还要提一下,您可以使用std::forward 将参数从callme 传递到func1...funcn。如果有足够的决心,您可以从它应该使用的函数列表中自动创建callme 的签名,使其更加透明。
  • 你问我为什么要这个。看看这篇文章(di.unipi.it/~nids/docs/templates_vs_inheritance.html)。在这里,当您使用模板执行策略模式(不确定它是否真的是策略模式)时,您初始化对象,而不是通过构造函数,而是调用 init 函数。我想确保用户已经这样做了,但在编译时。
  • @TomásBadan 在这个例子中,我认为init() 方法没有任何需要。事实上,它们可以被构造函数替换。因此,无论之前在哪里和谁调用init(),现在都将简单地将参数传递给构造函数或工厂(将它们传递给构造函数)。
  • @Drop 正如该文本中所说:“(...)编写器类实现... init 方法...进行对象的初始化...因为我们将它们定义为记录器的基类,而这一类不能具有所有可能的超类的“构造函数”。(...)“。因此,即使将 init 参数传递给基构造函数,问题是用户必须调用 init 来初始化基对象,否则它们很可能会被默认构造函数初始化。
  • @TomásBadan 我们真的可以在基类中使用任何参数组合的构造函数(可变参数模板魔术)。还有其他解决方案。顺便说一句,本文中描述的模式与“策略”没有共同之处。它被称为Curiously recurring template pattern (CRTP)。但是这个问题与强制执行函数调用的顺序无关。由于 SO 规则,您必须打开另一个主题来讨论它。
【解决方案2】:

没有真正的方法可以在编译时强制执行此操作。这是对象本身需要通过运行时检查来强制执行的事情

【讨论】:

    【解决方案3】:

    想到的一种方法是让 func1() 返回一个对象,它的作用类似于 func2() 的代理:

    class Foo {
    public:
        Proxy func1();   
    private:
        void func2();
        friend class Proxy;
    };
    
    class Proxy {
    private:
        explicit Proxy(Foo& f) : f_(f) {}
    public:
        void func2() {
            f_.func2();
        }
        friend class Foo;
    };
    
    Foo f;
    f.func1().func2();
    

    另一种方法(也是我最喜欢的)是让 func1() 成为构造函数或使用其他类,在其构造函数中调用 func1():

    class Foo
    {
    private:
        void func1(), func2();
        friend class FooUser;
    };
    
    class FooUser
    {
    public:
        explicit Proxy(FooUser& f) : f_(f) {
            f.func1();
        }
    
        void func2() {
            f_.func2();
        }
    };
    

    【讨论】:

      【解决方案4】:

      编译器无法强制执行函数调用的顺序,因为通常只能在运行时确定。但它可以在对象可以使用之前强制执行对象初始化。因此,获得编译时检查的最佳方法是执行 func1 在构造函数中所做的任何事情——要么是 Foo 的构造函数的一部分,要么是为了调用 func2 而需要创建的辅助对象。

      【讨论】:

        【解决方案5】:

        我真的不建议这样做,但是如果您出于调试目的需要它,可以尝试以下操作。 将每次调用 func1() 更改为

        #define FLAG
        func1();
        

        和 func2() 到

        #ifdef FLAG
        func2();
        #else
        #error func1 should be called first!
        #endif
        

        因此,如果 func2() 将在文本的上方然后 func1 被提及,您将收到编译时错误。这并不意味着它会在执行过程中真正被提前调用。

        【讨论】:

          猜你喜欢
          • 2020-07-07
          • 1970-01-01
          • 1970-01-01
          • 2011-08-21
          • 2012-09-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-06-15
          相关资源
          最近更新 更多