スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

テンプレート化されたクラスの仮想継承

まずは、ソースを見ていただこう。ABCクラスがAとBの共通の親で、CがAとBを多重継承しているという、いわゆる菱形継承ダイアグラムであるが、これにテンプレートがからんでいる。

ABC.h:

#ifndef ABC_H
#define ABC_H

template <class T> class ABC {
public:
ABC();
};
#endif

ABC.cpp:

#include "ABC.h"
#include <iostream>

template <class T> ABC<T>::ABC()
{
std::cout<<"ABC::ABC()" << std::endl;
}

namespace ABC_Template {
void instantiateTemplates(){
ABC<int> abc;
}
};

A.h:

#ifndef A_H
#define A_H

#include "ABC.h"

template <class T> class A : virtual public ABC<T>
{
public:
A();
};

#endif

A.cpp:

#include "A.h"
#include <iostream>

template <class T> A<T>::A()
: ABC<T>()
{
std::cout << "A::A()" << std::endl;
}

namespace A_Template {
void instantiateTemplates(){
A<int> a;
}
};


B.h:

#ifndef B_H
#define B_H

#include "ABC.h"

template <class T> class B : virtual public ABC<T>
{
public:
B();
};
#endif

B.cpp:

#include "B.h"
#include <iostream>

template <class T> B<T>::B()
: ABC<T>()
{
std::cout << "B::B()" << std::endl;
}

namespace B_Templates {
void instantiateTemplates(){
B<int> b;
}
};

C.h:

#ifndef C_H
#define C_H

#include "A.h"
#include "B.h"

template <class T> class C : public A<T>, public B<T>
{
public:
C();
};

#endif

C.cpp:

#include <iostream>
#include "C.h"

template <class T> C<T>::C()
: A<T>(), B<T>()
{
std::cout << "C::C()" << std::endl;
}

namespace C_Templates {
void instantiateTemplates(){
C<int> c;
}
};

main.cpp:

#include "C.h"

int main()
{
C<int> c;
return 0;
}


これを、

g++ -o test main.cpp C.cpp A.cpp B.cpp ABC.cpp

とすると、

/tmp/cczOxvhL.o: In function `C::C()':
C.cpp:(.text._ZN1CIiEC1Ev[C::C()]+0x2c): undefined reference to `A::A()'
C.cpp:(.text._ZN1CIiEC1Ev[C::C()]+0x48): undefined reference to `B::B()'
collect2: ld はステータス 1 で終了しました

のように、中間クラスのコンストラクタのリンクに失敗する。なお、テンプレートを用いていなければ、上の例は問題なくコンパイルできる。それは各クラスのcppファイルの中で、リンク時に必要になる具体的な関数のインスタンスをあらかじめ明示的に作成しているからである。各ファイル中で、namespaceで囲んだinstantiateTemplates()という関数がそれである。明示的に呼び出さない限り、この部分のコードが実行されることはないが、(テンプレートを用いていない場合は少なくとも)リンクはされる。

で、テンプレートを用いた場合、これだけではうまくいかない訳であるが、man g++でテンプレートに関係のある部分を見てみたところ、-frepo というオプションが、リンク時にテンプレートの自動インスタンス化を行ってくれるとある。ただし、このオプションを指定するのは-cオプションでオブジェクトを生成するフェーズのときのようである。

そこで、以下のように簡単なMakefileを書いてみる。

Makefile:
CXXFLAGS = -frepo
OBJECTS = main.o A.o B.o C.o ABC.o

all:test

test: $(OBJECTS)
g++ -o test $(OBJECTS)

これで、makeを実行してみると、

$make
g++ -frepo -c -o main.o main.cpp
g++ -frepo -c -o A.o A.cpp
g++ -frepo -c -o B.o B.cpp
g++ -frepo -c -o C.o C.cpp
g++ -frepo -c -o ABC.o ABC.cpp
g++ -o test main.o A.o B.o C.o ABC.o
collect: ABC.cpp を再コンパイルしています
collect: B.cpp を再コンパイルしています
collect: A.cpp を再コンパイルしています
collect: C.cpp を再コンパイルしています
collect: 再リンクしています
collect: B.cpp を再コンパイルしています
collect: A.cpp を再コンパイルしています
collect: main.cpp を再コンパイルしています
collect: ABC.cpp を再コンパイルしています
collect: 再リンクしています
collect: main.cpp を再コンパイルしています
collect: 再リンクしています
collect: main.cpp を再コンパイルしています
collect: 再リンクしています
collect: main.cpp を再コンパイルしています
collect: 再リンクしています

のように、どうやらコンパイラが必要なテンプレートのインスタンスをうまく生成しつつ、リンクを行ってくれているようである。ちゃんと

./test
ABC::ABC()
A::A()
B::B()
C::C()

のように実行できた。

2013/02/13 追記
テンプレートのインスタンス生成用関数を独立したネームスペース内に閉じ込めてしまうと、後でリンクエラーを引き起こすことがあるようだ(g++ 4.6での場合)。コンパイラー、リンカーの挙動としてはどうなるのが正しいのか知識不足で分からないが、とりあえずはテンプレートインスタンス生成用の関数はグローバルネームスペース内に配置するのがよいようだ。当然関数名の衝突の危険が出てくるから、ソースファイル名を接頭辞にするなどの対応が必要になる。


Delicious Save this on Delicious
スポンサーサイト

玄箱Pro 2TB HDDへのDebian Squeezeのインストール

今時分はちょうど2TBのハードディスクが一番バイト単価的にお得なので、これに新規にDebian Squeezeをインストールすることにした。以前のシステムはOABIシステムから順次入れ替えてEABI化していたものだったが、今回はクリーンインストールでさっぱりと最初からEABIである。

基本的に、masezou hackedに沿ってインストールするが、2TBのディスクだと、玄箱Pro + Debian lenny + 2TB HDD 起動までにある通り、インストールは問題なく行えるが起動時にカーネルの読み込みに失敗する。つまり玄箱ProのUbootが「2TB超でGPT」だとカーネルを読み込めないらしい。

対処法としては上記にもいくつか紹介されているが、もうディスクを外してWindowsマシンにつないで、とかは面倒だったので、玄箱Pro標準のLinuxをフラッシュメモリから起動して、あらかじめこちらのfdiskでディスクパーティションを作成しておいた。sda1だけは、インストーラを置かなければならないので、ext3でフォーマットも行う。インストール後は/bootとして使うことにして、容量も余裕を持って2GBほど割り当てておいた。

こちらのfdiskは期待通り、GPTではなくMBR形式のパーティションテーブルを作成してくれた。ただし、sda1-sda3をプライマリパーティション、sda4を拡張パーティションとして1.4TBほど取り、その中に論理パーティションを二つほど作りたかったのだが、拡張パーティションを作るところまでは問題なかったが、その中に論理パーティションを作ろうとfdiskの「n」サブコマンドを使用すると、「No free space」でできなかったので、この拡張パーティションの部分はインストール中に作成することにした。つまり、今回は最終的なパーティションテーブルは

root@debian:~# fdisk -l /dev/sda

Disk /dev/sda: 2000.4 GB, 2000398934016 bytes
255 heads, 63 sectors/track, 243201 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk identifier: 0x000a9e9e

Device Boot Start End Blocks Id System
/dev/sda1 * 1 250 2008093+ 83 Linux
/dev/sda2 251 37599 300005842+ 83 Linux
/dev/sda3 37600 62499 200009250 83 Linux
/dev/sda4 62500 243202 1451490305 5 Extended
/dev/sda5 62500 74657 97654784 83 Linux
/dev/sda6 74657 74719 498688 82 Linux swap / Solaris
/dev/sda7 74719 243202 1353334784 83 Linux

という形になったのだが、このうちsda1からsda3までを玄箱Proの標準Linuxのfdiskで作成し、ブートフラグもsda1につけておく。あとはインストール時に作成、ということである。これで問題なくブートできる。

もう一点、インストーラのconfig-debianスクリプトであるが、カーネルのファイル名がuImage.buffalo、initrdがinitrd.buffaloであることを仮定している。また、インストーラをおくパーティションがext2であることを仮定している。私はフラッシュメモリの設定を書き換えてカーネルファイル名が変わっていたので、最初インストーラが起動しなかった。同じようにカーネルファイル名を変更してしまっている人は、必要に応じ、

$SETENV bootcmd 'ide reset; ext2load ide 0:1 $(default_kernel_addr) /$(kernel)a'
echo "done."

の行の前あたりに

$SETENV kernel uImage.buffalo
$SETENV initrd initrd.buffalo

も追加しておけばよかろう。

また、上記のような事情で、sda1(/bootパーティション)はあらかじめext3で作成、フォーマットしたものをインストール後もそのまま使用するようにしたかったので、スクリプト冒頭近くの

path=$(mount | grep ext2 | sed -n '/sda1/ {s/\/dev\/sda1 on \(.*\) type.*/\1/; p
}')
if [ -z "$path" ]; then
echo "You have to create an ext2 filesystem on /dev/sda1"
exit 1
fi

のext2をext3に変更してからこのスクリプトを実行した。

なお、これでインストールは問題なく実行できるが、インストーラが作成したext3やext4のパーティションは、玄箱Proの標準Linuxでサポートされていない拡張機能を使用しているため、標準Linux側からマウントできないようである。つまりこのままだと何か問題が起きたときに標準Linux側から起動してマウント、修正という操作ができないので、用心のために一つext2の代替ルートパーティションと代替カーネルを用意しておいて、最低そちらはUbootから起動できるようにしておくという手はあるかもしれない。

Delicious Save this on Delicious

玄箱Pro 標準Linux起動のメモ

ずっと安定して使用していた玄箱Proであるが、しばらく前にディスクが故障した。幸いにして必要なデータ等は、代替スーパーブロックを指定して一時的にマウントすることで吸い出すことができた。

この機に、HDDを2TBのものに換装することにする。で、毎回何か起きる度に「玄箱Proの本」を引っ張り出してUbootのコマンドを見ながら、えーとデフォルトのカーネルってどこにあったっけなあ、ファイル名は…ルートデバイスはなんだっけ…と右往左往するので、ここにまとめておこう。

故障が起きたら、電源再投入→2度目のカウントダウンのところで何かキーを押してUbootプロンプトに入る。

フラッシュメモリからの起動

玄箱Proの標準カーネルをブートするには、フラッシュメモリから標準カーネルをロードした上で、ルートデバイスとして/dev/mtdblock2を使用する。すなわち

setenv bootargs console=ttyS0,115200 root=/dev/mtdblock2 rw panic=5 BOOTVER=1.09
nboot 0x00100000 0 0x00020000
bootm 0x00100000

で起動する。起動した後は、ユーザ名root、パスワードkuroadminでログインする。

なお、起動した標準Linuxからは、nvram -c printenv でUboot環境変数の設定を見ることができ、

nvram set 変数名 値

で変数の値を変更できる。

Uboot上で直接setenvを使用して設定した値は一時的なものなので、必要に応じてsaveenvを実行してフラッシュへの書き込みを行う。

ハードディスクからの起動

直接起動する場合
環境変数bootcmdおよび、そこから参照されるkernel, initrd、そしてbootargsを設定する。
setenv bootcmd ide reset; ext2load ide 0:1 $(default_kernel_addr) /$(kernel)
setenv kernel uImage.buffalo (あるいは新しく作成したカーネル(uImage形式))
setenv initrd initrd.buffalo (あるいは新しく作成したinitrd)
setenv bootargs console=ttyS0,115200 root=/dev/sda2 rw panic=5 BOOTVER=1.09 (ルートデバイスが/dev/sda2の場合)
boot

いったんカーネルをハードディスクからメモリにロードして起動する場合
bootargsは使用されるので、上記と同じく適切に設定。
ide reset
ext2load ide 0:1 0x00100000 /uImage.buffalo
この例は/dev/sdaの一番最初のパーティション(0:1)上のディレクトリに置かれたuImage.buffaloというファイル名のカーネルをロードする場合。状況に応じて変更せよ。
メモリにカーネルがロードできたら
bootm 0x00100000
で起動。

Delicious Save this on Delicious
プロフィール

GM3D

Author:GM3D
FC2ブログへようこそ!

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
FC2カウンター
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。