添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
3
1

インクルード順序を変えるとBoostがコンパイルエラーになる

Posted at

Boostを使ってコードを書いているときに遭遇したエラーです。

  • Visual Studio 2019 (cl.exe 19.29.30136)
  • boost 1.84.0
  • OpenSSL 3.0.0
  • 再現コード

    再現可能なコードは以下の通りです。

    main.cpp
    #include <boost/asio.hpp>
    #include <openssl/ssl.h>
    #include <windows.h>
    #include <wincrypt.h>
    #include <boost/asio/ssl.hpp>
    
    D:\win\boost\boost_1_84_0\boost/asio/ssl/impl/rfc2818_verification.ipp(107): error C2065: 'name': 定義されていない識別子です。
    D:\win\boost\boost_1_84_0\boost/asio/ssl/impl/rfc2818_verification.ipp(110): error C2065: 'name': 定義されていない識別子です。
    D:\win\boost\boost_1_84_0\boost/asio/ssl/impl/rfc2818_verification.ipp(112): error C2065: 'name': 定義されていない識別子です。
    

    不可解かもしれませんが、一つでもインクルードの順序が変わったり、コメントアウトしたりするとエラーが発生しなくなります。

    なぜ発生する?

    なんとなくしか原因を把握できていないのですが、最初の#include <boost/asio.hpp>を削除すると、エラーが変化します。

    #include <openssl/ssl.h>
    #include <windows.h>
    #include <wincrypt.h>
    #include <boost/asio/ssl.hpp>
    
    D:\win\boost\boost_1_84_0\boost/asio/detail/socket_types.hpp(24): fatal error C1189: #error:  WinSock.h has already been included
    

    これはBoostが吐いたエラーで、推測するにWinSock.hがあらかじめインクルードされている状況を想定していないと思われます。通常はこのチェックにより、問題の所在を明らかにするはずです。

    しかし、最初の例ではこのインクルードチェックが行われていません。どういうことかというと、

    #include <boost/asio.hpp>
    // boost/asio/detail/socket_type.hppをインクルード、WinSock.hをチェック
    #include <openssl/ssl.h>
    #include <windows.h>
    #include <wincrypt.h>
    // WinSock.hなどなどをインクルード
    #include <boost/asio/ssl.hpp>
    // boost/asio/detail/socket_type.hppをインクルードするも、インクルードガードにより無視される
    // -> WinSock.hのチェックをすり抜けてエラー
    

    という流れになっていると思われます。

    今回はboost関連のインクルードを分散して書いてしまった結果、間に他のヘッダが入り込むことで不可解なエラーが発生してしまいました。Boost関連のヘッダをまとめてインクルードすれば、こういった問題は起こりません。

    example1.cpp
    #include <boost/asio.hpp>
    #include <boost/asio/ssl.hpp>
    #include <openssl/ssl.h>
    #include <windows.h>
    #include <wincrypt.h>
    
    example2.cpp
    #include <openssl/ssl.h>
    #include <windows.h>
    #include <wincrypt.h>
    #include <boost/asio.hpp>
    #include <boost/asio/ssl.hpp>
    // errorになるが、#error: WinSock.h has already been included となるので解決できる
    

    今回のコードはいささか恣意的にも見えるので、わざわざこんなコードを書こうとは思わないかもしれません。しかし、いろんなライブラリがそれぞれインクルードをした結果、このような構造になってしまうことはしばしばあると思います。

    普遍的な対策としては、Boostのような複数のファイルからなるライブラリをインクルードするときに、それ専用のヘッダファイルを作ってしまうということです。

    boost_wrapper.hpp
    #ifndef MY_BOOST_WRAPPER_HEADER_INCLUDED
    #define MY_BOOST_WRAPPER_HEADER_INCLUDED
    #include <boost/asio.hpp>
    // その他いろいろインクルード
    #endif
    
    lib.hpp
    #include <boost_wrapper.hpp>
    void my_some_func() {}
    
    main.cpp
    #include <boost_wrapper.hpp>
    #include <lib.hpp>
    int main(){}
    

    複数のファイルからBoostなどをインクルードしても、きちんと順序を守ってインクルードされます。が、このような方法がいつもとれるとは限らないし、なにより非効率です。
    (#define USE_XXXなどインクルード前に記述が必要な場合などはwrapperを作った方がいいかもしれません)

    他の対策としては、別々にコンパイルしてリンクする方法です。今回のケースでは、

    boost/asio.hppに依存したコード wincrypt.hに依存したコード

    の両方をinline展開したくてヘッダに書いてインクルードした結果、衝突が起こってしまいました。この二つを別々のオブジェクトとしてコンパイルした後、必要なインターフェースだけヘッダファイルに摘出してリンクすれば衝突を避けることができます。

    とはいえ、c++のプリプロセッサは基本的にグローバルスコープである以上、他のライブラリと何らかの要因で衝突する可能性は排除できません。何らかの実装ミスがない限り、こういったエラーが発生するのはごく稀なのであまり気にしなくてよいと思います。

    3
    1
    3

    Register as a new user and use Qiita more conveniently

    1. You get articles that match your needs
    2. You can efficiently read back useful information
    3. You can use dark theme
    What you can do with signing up
    3
    1