【C++】ライブラリとリンク

ライブラリ

複数の機能をまとめた部品

静的リンク

プログラムを動かす際に、ライブラリ内のルーチンを実行可能プログラムに含める方法
これに対応したライブラリが静的リンクライブラリ

動的リンク

プログラムの実行時に、メモリ上でリンクを行う方法
これに対応したライブラリが動的リンクライブラリ(共有ライブラリ)

静的リンクと動的リンクの違いの確認

静的リンクライブラリの作成
MyStaticLibプロジェクトを作成し、以下の2つのファイルを作成する
MyStatic.h

#pragma one

class MyStatic
{
public:
  int Calc(int a, int b);
}
MyStatic.cpp

#include "MyStatic.h"

int MyStatic::Calc(int a, intb)
{
  return a + b;
}
動的リンクライブラリの作成
MyDynamicLibプロジェクトを作成し、以下の2つのファイルを作成する
MyDynamic.h

#pragma one
#ifdef MYDYNAMICLIB_EXPORTS
#define MYDYNAMICLIB_API __declspec(dllexport)
#else
#define MYDYNAMICLIB_API __declspec(dllimport)
#endif

class MyDynamic
{
public:
  MYDYNAMICLIB_API int Calc(int a, int b);
}
MyDynamic.cpp

#include "MyDynamic.h"

int MyDynamic::Calc(int a, int b)
{
  return a + b;
}
実行ファイルの作成
上記で作成した2つのライブラリを使用するファイルを作成する

#include <iostream>
#include <MyDynamic.h>
#include <MyStatic.h>

int main()
{
  auto myDynamic = std::make_unique<MyDynamic>();
  auto myDynamicRet = myDynamic->Calc(5, 10);
  std::cout << myDynamicRet <<std::endl;
  
  auto myStatic = std::make_unique<MyStatic>();
  auto myStaticcRet = myStatic->Calc(1, 20);
  std::cout << myStaticRet <<std::endl;
}
実行①
実行ファイルを実行すると、15,21が表示される。
実行②
各Calc関数を足し算から引き算に変更後にビルドして、実行ファイルを実行すると、-5, 21が表示される。

【C++】リンカー

リンカー

関数や変数は、宣言(declaration)と定義(definition)することが出来る。
宣言(declaration)
型の情報や名前などのシンボルの概要をコンパイラに伝える
以下のような関数を宣言すると、コンパイラにMyPrint(symbol)という関数が存在する(function exists)ということを伝える。

void MyPrint(std::string const& msg);
定義(definition)
シンボルの詳細を示し、その名前が参照しているメモリを確保する。

void MyPrint(std::string const& msg)
{
 std::cout << msg << std::endl;
}
リンカーの挙動1
以下のようなコードの場合、コンパイルもビルドも成功する。
MyPrintの定義が存在しないが、MyPrintを使用している箇所がないためリンカーが探しにいくことはない。

#include <iostream>

void MyPrint(std::string const& msg);

int Addition(int a, int b) {
	// MyPrint("Use Addition");
	return a + b;
};

int main() {
	auto t = Addition(1, 2);
}
リンカーの挙動2
リンカーの挙動1との違いは、AdditionでMyPrintを使用するが、Addition自体は使用しないようにしている。
一見すると、リンカーの挙動1と同様に、コンパイルもビルドも成功するように思えるが、ビルド時にリンキングエラーが発生する。
これは、ファイル内でAdditionが使用されていないということは、他のファイルで使用される可能性があるため、リンカーがMyPrintを探すため。

#include <iostream>

void MyPrint(std::string const& msg);

int Addition(int a, int b) {
	MyPrint("Use Addition");
	return a + b;
};

int main() {
	//auto t = Addition(1, 2);
}

【C++】実行までの流れ

実行ファイルの作成

テキストファイル
C++の文法に従って、人間が分かるように書かれたテキストファイル(拡張子がcpp)。
Javaとは異なり、クラス名とファイル名が違っても良い。
プリプロセス
プリプロセッサがプリプロセスステートメントの加工を行う。
代表的に#includeディレクティブがあり、プリプロセッサが#includeで指定したファイルをコピー&ペーストしてくれる。
極端な例ではあるが、以下の場合、プリプロセッサが※の箇所にPreprocess.hをペーストしてくれる。
Preprocess.h

}
Main.cpp

#include <iostream>

int main(){
 std::cout << "Test" << std::endl;
#include "Preprocess.h" // ※
プリプロセスの結果のファイルは*.iファイルで、プロジェクトのプロパティ>[構成プロパティ]>[C/C++]>[プリプロセッサ]>[ファイルの前処理]を"はい(/P)"に指定すると出力される。
コンパイル
C++のコンパイラはテキストファイル単位(分割コンパイル)で実行可能なファイル(バイナリ形式)に変換する。
コンパイル時にエラー(コンパイルエラー)が発生した場合のエラーメッセージはC~
コンパイルされた結果は*.objファイルとして出力される。
ファイルを保存後"Ctrl+F7"でコンパイルすることが可能。
バイナリ形式の一歩手前のアセンブリファイルを出力するためには、プロジェクトのプロパティ>[構成プロパティ]>[C/C++]>[出力ファイル]>[アセンブリの出力]を"アセンブリコードのみ(/FA)"に指定
リンキング
結構躓くところ
コンパイルに記載の通り、C++は分割コンパイルであり、コンパイル結果の各オブジェクトファイルは他のオブジェクトのことが分からない。
そこで、リンカがグローバル変数や関数の呼び出し参照を結合する。
リンキングが失敗した際のエラー(リンキングエラー)が発生した場合のエラーメッセージはLNK~
エントリーポイント(main関数)
名前はmainでなくても良いが、一般的にmainとされている。
特殊な関数で、戻り値がint型であるが、returnしなくても良い。

【C#】Enumerable.OfType<TResult>(IEnumerable) メソッド

OfType<TResult>

配列やリストなどのシーケンス内に、指定した型に変換できる要素を取得する。


public static IEnumerable<TResult> OfType<TResult>(this IEnumerable source)
{
    if (source == null) throw Error.ArgumentNumm("source");
    return OfTypeIterator<TResult>(source);
}

static IEnumerable<TResult> OfTypeIterator<TResult>(IEnumerable source)
{
    foreach (object obj in source)
    {
        if (obj is TResult) yield return (TResult)obj;
    }
}

 

自作クラスで確認


internal abstract class MyElement : IEnumerable<MyElement>, IEnumerable
{
    internal List<MyElement> children;
    
    internal MyElement()
    {
        this.children = new List<MyElement>();
    }
    
    public IEnumerator<MyElement> GetEnumerator()
    {
        foreach (var child in children) yield return child;
    }
    
    IEnumerator IEnumerator.GetEnumerator()
    {
        return ((IEnumerable)children).GetEnumerator();
    }
}

internal class MyTableRow : MyElement
{
    internal void AddChild(MyElement myElement) this.children.Add(myElement);
}

internal class MyTableCell : MyElement
{
}

internal class MyTableRowProperty : MyElement
{
}

internal class Dummy : MyElement
{
}

static void Main()
{
    var tr = new MyTableRow();
    tr.AddChild(new MyTableCell());
    tr.AddChild(new MyTableRowProperty());
    tr.AddChild(new MyTableCell());
    tr.AddChild(new MyTableCell());
    tr.AddChild(new MyTableRowProperty());
    
    var tcs = tr.OfType<MyTableCell>();
    System.Console.WriteLine(tcs.Count()); // 3
    var trPrs = tr.OfType<MyTableRowProperty>();
    System.Console.WriteLine(trPrs.Count()); // 2
    var dummys = tr.OfType<Dummy>();]
    System.Console.WriteLine(dummys.Count()); // 0
}

【C#】IEnumerable<T>インタフェースの実装

IEnumerable<T>インタフェースの実装

 

ttkcd.hatenablog.com

 

に記載の通り、IEnumerable<T>インタフェースは抽象メソッドGetEnumerator()を持っているため、IEnumerable<T>インタフェースを実装する際には、GetEnumerator()を実装する必要がある。
また、IEnumerator<T>を継承するとIEnumeratorも継承することになるため、IEnumeratorのメソッドも実装する必要がある。

internal class MyCollection<T>:IEnumerable<T>
{
    private List<T> elems;
    
    internal MyCollection(int num)
    {
        elems = new List<T>(num);
    }
    
    public IEnumerator<T> GetEnumerator()
    {
        foreach (var elem in elems) yield return elem;
    }
    
    public IEnumerator GetEnumerator()
    {
        return GetEnumerator();
    }
}

【C#】IEnumerable<T>

IEnumerable<T>インタフェース

ジェネリックコレクション(System.Collections.Generic)に対する単純な反復処理をサポートする列挙子を公開する。
IEnumerator<T>インタフェースを返す抽象メソッドGetEnumerator()を持つ。

 

ttkcd.hatenablog.com

 

 

【C#】IEnumerableインタフェースの実装

IEnumerableインタフェースの実装

 

ttkcd.hatenablog.com

 

に記載の通り、IEnumerableインタフェースは抽象メソッドGetEnumerator()を持っているため、IEnumerableインタフェースを実装する際には、GetEnumerator()を実装する必要がある。

internal class MyCollection : IEnumerable
{
    private readonly int[] m_array;
    
    internal MyCollection(int number)
    {
        m_array = new int[number];
        for (int i = 0; i < number; ++i)
        {
            m_array[i] = i;
        }
    }
    
    IEnumerator IEnumerable.GetEnumerator()
    {
        // IEnumeratorインタフェースを返す処理
    }
}

GetEnumerator()の実装


IEnumerator IEnumerable.GetEnumerator()
{
    return m_array.GetEnumerator();
    for  (int i = 0; i < m_array.Length; ++i)
    {
        yield return m_array[i];
    }
    foreach (var item in m_array)
    {
        yield return item;
    }
}