【问题标题】:Static initialization with necessary sideeffects is optimized away具有必要副作用的静态初始化被优化掉
【发布时间】:2017-07-05 12:19:54
【问题描述】:

我想向一个类的每个子类发送一个信号。 为了实现这一点,我使用了 boost 中的 signals2 库。由于使用了代码,我需要使用 CRTP。

最小示例,包含在头文件中:

class A {
    static boost::signals2::signal<void()>& getSignal() {
        static boost::signals2::signal<void()> signal;
        return signal;
    }

public:
    static void sendSignal() {
        getSignal()();
    }

protected:
    template <typename slot_t>
    static boost::signals2::connection addSlot(slot_t slot) {
        return _entityToBeDeleted().connect(slot);
    }
};

template <typename concrete_B>
class B : A {
    static boost::signals2::connection _installHandler() {
        return A::addSlot([]() {
            //Do something
        });
    }

    static const boost::signals2::connection connection;
};
template<typename concrete_B>
const boost::signals2::connection B<concrete_B>::connection = _installHandler();

class C : public B<C> {
    //Concrete subclass
};

某个想要通知子类的客户端类调用A::sendSignal,它调用连接到信号的所有槽。此通知的具体内容与此问题的目的无关。如您所见,需要B&lt;concrete_B&gt;::connection 的静态初始化器的副作用才能使其工作,但由于对象本身从未使用(也不需要),因此跳过了完整的初始化。

问题是:编译器完全优化了对 _installHandler() 的调用(我在 Visual Studio 2017 中使用 MSVC)。我通过编写一个额外的测试用例来验证这一点,它明确地测试连接对象是否有效。当我以这种方式使用对象时,代码工作并且所有其他测试用例都通过了。当我删除额外的测试用例时,其他测试用例再次失败。

我尝试按照this question 的回答中的建议声明静态变量 volatile,但没有帮助。

如何防止静态连接对象的初始化被优化掉。或者:如何确保通过模板实例化创建的具有副作用的函数始终在可执行文件的开头调用一次,即使返回的对象从未实际使用过;理想情况下在静态初始化期间。

我考虑过在这个类的另一个静态初始化器(std::cout &lt;&lt; connection.connected() &lt;&lt; '\n'; 或类似的东西)中以某种方式使用连接,它已经可以安全地被调用(因为肯定使用了这个初始化的对象),但这看起来很讨厌破解。

编辑:我实现了“黑客”并且它完美地工作,但是如果有一种方法可以以“更清洁”的方式实现所需的行为,我很感兴趣。

【问题讨论】:

  • 如果没有其他代码 odr 使用该目标文件中定义的任何内容,某些链接器将跳过在可执行文件中包含目标文件...
  • 您的最小示例至少应该包含头文件的使用方式。阅读:minimal reproducible example
  • @aschepler 我知道,问题是如果静态初始化的相关部分不是返回的对象本身(不需要),而是函数(即对signal::connect的调用。
  • 仅从头文件中并不清楚是什么翻译单元导致了这种情况。
  • 注意,由于初始化B&lt;concrete_B&gt;::connection涉及到一个非constexpr函数调用,所以只能在动态初始化阶段进行。

标签: c++ visual-studio templates boost static


【解决方案1】:

类模板的成员按需实例化。一种可能的解决方法是从B 的构造函数或析构函数中引用B&lt;concrete_B&gt;::connection

B() { static_cast<void>(&connection); }

【讨论】:

  • 虽然我知道这个技巧来标记故意未使用的函数参数,但我没想过在这里使用它。天才。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-12-18
  • 2016-05-15
  • 1970-01-01
  • 2017-08-16
  • 2015-08-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多