1
1
実現したいこと
- C++の実行コードをヘッダに書くか、ソースに書くかについて、皆さんのご意見をうかがいたい
- 例えば「自分はベストプラクティスを知っているので広めたい!」という意見
- 例えば「自分はこういうケースでいつもこうしている」という意見
- 例えば「自分はよく知らないけど、こういうツールを知っている」という意見
- etc...
前提
- C++11(以降)
- 公開範囲については皆さんが想定されるケースを決めてください(言及しない場合はオープンソース)
- 規模感については皆さんが想定されるケースを決めてください(言及しない場合は数k行程度以内)
- 外部コードの有無については、皆さんが想定されるケースを決めてください(言及しない場合は自身がアプリから使用されるライブラリ)
- 処理系についてはgcc9以降限定
- プラットフォームについては限定しませんが、OSはあるものとしてください
発生している問題・エラーメッセージ
- (A)ヘッダに書く場合は、公開したくないコードまで公開する必要がある
- (B)ソースに書く場合は、テンプレートのインスタンス化が悩ましい
- (C)ヘッダに書く場合は、同じものが複数展開されるため、それぞれでコードが異なりうる懸念がある
(B),(C)については以下コードで例示します
該当のソースコード
環境構築用のスクリプトです。最後にビルドして実行します。
bash
1#!/bin/sh 2cat >Makefile <<EOF 3CC=g++ 4CXXFLAGS=-g -std=c++11 -Wall 5all: main main_lib main_diff 6clean: 7 rm -f *.o *.a main main_lib main_diff 8main: main.o 9main_lib: main_lib.o libsub.a 10 \$(CC) main_lib.o -L. -lsub -o main_lib 11libsub.a: libsub.a(sub_lib.o) 12main_diff: main_diff.o libsub2.a 13 \$(CC) main_diff.o -L. -lsub2 -o main_diff 14main_diff.o: main_diff.cpp sub_diff_v1.h 15 cp sub_diff_v1.h sub_diff.h 16 \$(CXX) \$(CXXFLAGS) \$(CPPFLAGS) \$(TARGET_ARCH) -c main_diff.cpp 17sub2_diff.o: main_diff.cpp sub_diff_v2.h 18 cp sub_diff_v2.h sub_diff.h 19 \$(CXX) \$(CXXFLAGS) \$(CPPFLAGS) \$(TARGET_ARCH) -c sub2_diff.cpp 20libsub2.a: libsub2.a(sub2_diff.o) 21EOF 22cat >main.cpp <<EOF 23#include <iostream> 24#include "sub.h" 25int main() { 26 Hoge hoge; 27 hoge.print(std::cout); 28 return 0; 29} 30EOF 31cat >main_diff.cpp <<EOF 32#include <iostream> 33#include "sub_diff.h" 34#include "sub2_diff.h" 35int main() { 36 Hoge hoge; 37 hoge.print(std::cout); 38 sub2(); 39 return 0; 40} 41EOF 42cat >main_lib.cpp <<EOF 43#include <iostream> 44#include "sub_lib.h" 45int main() { 46 Hoge hoge; 47 hoge.print(std::cout); 48 return 0; 49} 50EOF 51cat >sub.h <<EOF 52#pragma once 53#include <ostream> 54struct Hoge { 55 template<typename T> void print(T& out) { out << "ほげ" << std::endl; } 56}; 57EOF 58cat >sub2_diff.cpp <<EOF 59#include <iostream> 60#include "sub_diff.h" 61#include "sub2_diff.h" 62void sub2() { 63 Hoge hoge; 64 hoge.print(std::cout); 65} 66EOF 67cat >sub2_diff.h <<EOF 68#pragma once 69void sub2(); 70EOF 71cat >sub_diff.h <<EOF 72#pragma once 73#include <ostream> 74struct Hoge { 75 template<typename T> void print(T& out) { out << "ふが" << std::endl; } 76}; 77EOF 78cat >sub_diff_v1.h <<EOF 79#pragma once 80#include <ostream> 81struct Hoge { 82 template<typename T> void print(T& out) { out << "ほげ" << std::endl; } 83}; 84EOF 85cat >sub_diff_v2.h <<EOF 86#pragma once 87#include <ostream> 88struct Hoge { 89 template<typename T> void print(T& out) { out << "ふが" << std::endl; } 90}; 91EOF 92cat >sub_lib.cpp <<EOF 93#include <ostream> 94#include "sub_lib.h" 95template<typename T> 96void Hoge::print(T& out) { 97 out << "ほげ" << std::endl; 98} 99template void Hoge::print(std::ostream&); 100EOF 101cat >sub_lib.h <<EOF 102#pragma once 103struct Hoge { 104 template<typename T> void print(T& out); 105}; 106EOF 107make 108./main 109./main_lib 110./main_diff
(B)/(C)の説明
まずは基本となるmain.cppとsub.hを見てください。
main.cpp
c++
1#include <iostream> 2#include "sub.h" 3int main() { 4 Hoge hoge; 5 hoge.print(std::cout); 6 return 0; 7}
sub.h
c++
1#pragma once 2#include <ostream> 3struct Hoge { 4 template<typename T> void print(T& out) { out << "ほげ" << std::endl; } 5};
何の変哲もない「ほげ」って出力するだけのコードです。
ヘッダにコードが記述されているので、モジュールも1つだけになっている単純な例です。
自分のコードがsub.hで、これを公開すると思ってください。
(B)ソースに書く場合は、テンプレートのインスタンス化が悩ましい
sub.hの実行コードをソースに移し、(静的)ライブラリにする形態がmain_lib.cpp、sub_lib.h、sub_lib.cppになります。
main_lib.cpp
c++
1#include <iostream> 2#include "sub_lib.h" 3int main() { 4 Hoge hoge; 5 hoge.print(std::cout); 6 return 0; 7}
sub_lib.h
c++
1#pragma once 2struct Hoge { 3 template<typename T> void print(T& out); 4};
sub_lib.cpp
c++
1#include <ostream> 2#include "sub_lib.h" 3template<typename T> 4void Hoge::print(T& out) { 5 out << "ほげ" << std::endl; 6} 7template void Hoge::print(std::ostream&);
気づきましたでしょうか?
sub_lib.cppの最終行に、テンプレートのインスタンス化用の定義が入っています。この行はstd::coutのクラスであるstd::ostreamが定義されています。これがないと main_lib.cpp 側の hoge.print(std::cout) 呼び出しでundefined referenceが発生しちゃうわけです。呼び出す側のコードで、T=std::outputstreamなメソッドを必要とするので、呼び出される側のコードでそのインスタンス化が必要になるということです。
つまり呼び出す側のコードで使いたいTが増えるごとに、呼び出される側にこの行が必要になるということになります。これが面倒だというのが(B)です。
(C)ヘッダに書く場合は、同じものが複数展開されるため、それぞれでコードが異なりうる懸念がある
説明のため、元のコードを以下のようにしています。
- main.cpp→main_diff.cpp
- sub.h→sub_diff_v1.h
さらに同じものを複数展開するために、
- sub.h→sub_diff_v2.h
- sub2_diff.h
- sub2_diff.cpp
というのを用意しています。ようはモジュールを2つに増やし、それぞれにsub.h相当をインクルードして呼び出させるということです。
sub2側はライブラリにして、main側からリンクします。
main_diff.cpp
c++
1#include <iostream> 2#include "sub_diff.h" 3#include "sub2_diff.h" 4int main() { 5 Hoge hoge; 6 hoge.print(std::cout); 7 sub2(); 8 return 0; 9}
sub_diff_v1.h
c++
1#pragma once 2#include <ostream> 3struct Hoge { 4 template<typename T> void print(T& out) { out << "ほげ" << std::endl; } 5};
sub_diff_v2.h
c++
1#pragma once 2#include <ostream> 3struct Hoge { 4 template<typename T> void print(T& out) { out << "ふが" << std::endl; } 5};
sub2_diff.h
c++
1#pragma once 2void sub2();
sub2_diff.cpp
c++
1#include <iostream> 2#include "sub_diff.h" 3#include "sub2_diff.h" 4void sub2() { 5 Hoge hoge; 6 hoge.print(std::cout); 7}
各.cppファイルでインクルードしてるファイルがsub_diff.hになっていますが、これは_v1/_v2というサフィックスがついたファイルをmake注にコピーして作成しています。何を意図しているかというと、main_diff側と、libsub2.a側で異なるバージョンのsub_diff.hを使ったケースの再現です。そのままビルドすると、main_diff側のコードに一本化されて動きますが、最適化させるとインライン展開されてそれぞれ異なるコードで動作します。
最後に
C++を使ってる人なら誰もが一度はどうしようかと悩むところだと思いますが、あっちを立てればこっちが立たずみたいな状況なので、意見を集めてみてもいいかなと思って、書いてみました。参考までに私は公開ソースなら全部ヘッダにしていて、非公開なら規模次第でソースにしてます。テンプレートは限定的な使い方にします。
※勘違いの指摘なども歓迎です
回答16件
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。