C++ 编译时检查一个函数是否在另一个函数之前调用

C++ Compile time check if a function called before another one

假设我有一个带有两个成员函数的 class。

class Dummy {
 public:
  void procedure_1();
  void procedure_2();
};

在编译时,我想确保 procedure_1procedure_2 之前被调用。执行此操作的正确方法是什么?

也许您可以使用代理来做到这一点-class。这个想法是,procedure_2 不能直接从外部访问(例如通过将其设为私有)。 procedure_1 会 return 某种允许访问 procedure_2.

的代理

下面的一些代码,虽然我不认为它干净或安全。如果你愿意,你仍然可以破坏系统。

IMO 这样的要求应该在没有明确验证的情况下处理,因为它非常麻烦并且不可能使其绝对安全。 相反,应该很好地记录依赖关系,这在 C++ 中似乎也是惯用的。如果函数使用不当,您会收到一条警告,可能会发生不好的事情,但没有什么能阻止您开枪。

class Dummy {
private:
    void procedure_2() { }

    class DummyProxy
    {
        private:
            Dummy *parent; // Maybe use something safer here
        
        public:
            DummyProxy(Dummy *parent): parent(parent) {}
            
            void procedure_2() { this->parent->procedure_2(); }
    };
    
public:
    [[nodiscard]] DummyProxy procedure_1() {
        return DummyProxy{this};  
    }  
};

int main()
{    
    Dummy d;
    // d.procedure_2(); error: private within this context
    
    auto proxy = d.procedure_1();  // You need to get the proxy first
    proxy.procedure_2();           // Then 

    // But you can still break the system:  
    Dummy d2;
    decltype(d2.procedure_1()) x(&d2); // only decltype, function is not actually called
    d2.procedure_2(); // ooops, procedure_1 wasn't called for d2
}

不是“检查”它,而是不允许它。不要公开允许以任何其他方式调用它的接口。公开一个允许 按指定顺序调用它的接口。例如:

// library.c
class Dummy {
private:
    void procedure_1();
    void procedure_2();
public:
    void call_Dummy_prodedure_1_then_something_then_produre_2(std::function<void()> f){
        procedure_1();
        f();
        procedure_2();
    }
};

您还可以让 procedure_2 从析构函数调用,procedure_1 从构造函数调用。

#include <memory>

struct Dummy {
private:
    void procedure_1();
    void procedure_2();
public:
    struct Procedures {
        Dummy& d;
        Procedures(Dummy& d) : d(d) { d.procedure_1(); }
        ~Procedures() { d.procedure_2(); }
    };
    // just a simple example with unique_ptr
    std::unique_ptr<Dummy::Procedures> call_Dummy_prodedure_1_then_produre_2(){
        return std::make_unique<Dummy::Procedures>(*this);
    }
};
int main() {
    Dummy d;
    auto call = d.call_Dummy_prodedure_1_then_produre_2();
    call.reset(); // yay!
}

以上方法可确保在一个翻译单元内对调用进行排序。要在多个源文件之间进行检查,生成最终的可执行文件,然后编写一个工具来检查生成的程序集,如果有两次或更多次调用 call_Dummy_prodedure_1_then_produre_2 函数,该工具将出错。为此,需要额外的工作来确保 call_Dummy_prodedure_1_then_produre_2 不能被编译器优化。

但是您可以创建一个只能由一个翻译单元包含的 header:

// dummy.h
int some_global_variable_with_initialization = 0;
struct Dummy {
    ....
};

并将上面的接口公开到 Dummy 或仅在该库中添加包装器声明。这样,如果多个源文件包含 dummy.h,链接器将出错并显示 multiple definitions 错误。

至于检查,你可以制作prodedure_1procedure_2一些宏,这些宏将扩展为编译器无法优化的带有一些标记的东西,比如汇编注释。然后您可以使用自定义工具检查生成的可执行文件,该工具将检查对 prodedure_1 的调用是否在 procedure_2.

之前