type traits(型特性)クラスの利用
またまたc++勉強ネタ。
traitsクラス
traits
クラスは、template
を利用したテクニックの一つです。
標準ライブラリや、boostなどで多用される手法の一つでもあります。
今回は、traits
クラスの基本的な利用法を勉強したいと思います。
traitsクラス
type traits
(型特性)は、型に関する特性を表現するために利用されるクラスです。
traits
には色々なバリエーションがありますが、まず例として、STLのiterator_traits
クラステンプレートを見てみましょう.
traitsを利用した例 std::iterator_traits
std::iterator_traits
は、イテレータに関する型特性を表現するためのクラスで、以下のような構成をしています.
以下のコードはcpprefjp - C++日本語リファレンスから引用しました。
namespace std { template <class Iterator> struct iterator_traits { using difference_type = typename Iterator::difference_type; using value_type = typename Iterator::value_type; using pointer = typename Iterator::pointer; using reference = typename Iterator::reference; using iterator_category = typename Iterator::iterator_category; }; // ポインタに対する特殊化 template <class T> struct iterator_traits<T*> { using difference_type = ptrdiff_t; using value_type = T; using pointer = T*; using reference = T&; using iterator_category = random_access_iterator_tag; }; template<class T> struct iterator_traits<const T*> { using difference_type = ptrdiff_t; using value_type = T; using pointer = const T*; using reference = const T&; using iterator_category = random_access_iterator_tag; }; }
コードを見ると、iterator_traits
は単にtypedef
された型情報を格納しているだけで、メンバを持っていません.
使用者は、適切にこのテンプレートを特殊化することによって、iterator
用に作られたアルゴリズムを利用することができるようになります
traits活用: タグ・ディスパッチ
std::iterator_traits
には、タグ・ディスパッチのために利用する iterator_category
という型を持っています
iterator_category
には、タグ(tag)と呼ばれる空クラスを指定します
標準で用意されているクラスは以下
std::input_iterator_tag
std::output_iterator_tag
std::forward_iterator_tag
std::bidirectional_iterator_tag
std::random_access_iterator_tag
さて、このiterator_category
をどう使うかというと、関数が引数の型によって選択できることを利用します
//こんな感じでディスパッチさせる template <typename InputIterator> void foo(InputIterator itr, std::input_iterator_tag){ } template <typename OutputIterator> void foo(OutputIterator itr, std::output_iterator_tag){ } template <typename Iterator> void foo(Iterator itr){ foo(itr, typename std::iterator_traits<Iterator>::iterator_category()); }
また、std::forward_iterator_tag
などは他のイテレータタグを継承しています
タグが変換可能であれば呼び出しを行えるようになるので、継承がうまく利用されていますね
以上、type traits
の基本でした。この他にもstatic
関数と組み合わせたり、traits
には様々な活用法があるみたいです
いろんなライブラリをよんで勉強していきたいですね!
君はプライベートメンバにアクセスできるフレンズなんだね!
いまだc++を勉強中です。今回はちょっとマイナーな機能、friend
について
friend
friend
を使えば、クラスのプライベートメンバにアクセスできる外部関数を指定できます。
private
を指定すると、基本的に外部からのアクセスは禁止されるのですが、friend
を使えば特例を作ることができる機能です。
この機能を使わなければいけないことは少ないと思いますが、覚えておくと便利かもしれません。
ちなみに、friend
指定はメンバ関数のように行いますが、アクセスが許可されるだけで、関数自体はメンバではありません。
つまりthis
ポインタは渡されませんので、参照やコピーを行ってオブジェクトを関数に渡す必要があります。
もちろん、static
メンバであれば、インスタンスは不要です。
使いみちを考えてみましたが、ポインタでリンクを構築する時とか?
トリッキーなので、一から設計をする際には、あまり選択したくない方法ではありますね
サンプルコードを以下に示します。
#include <iostream> class A; class B; class C; class A{ private: friend bool connectAB( A& a, B& b); //フレンド指定は宣言のように行います B* ptrb = nullptr; }; class B{ private: friend bool connectAB(A& a, B& b); A* ptra = nullptr; }; class C{ private: friend bool connectAB(A& a,B& b); static void foo(){ std::cout << "すごーい!" << std::endl; } }; bool connectAB(A& a,B& b){ //privateにアクセスできます a.ptrb = &b; b.ptra = &a; C::foo(); //staticなら、インスタンスは不要 return true; } int main(){ A a; B b; connectAB(a,b); return 0; //出力: //すごーい! }
NURBS曲線の特徴
CGの分野では解像度に非依存な形状を保存するために、パラメトリック曲線を利用することがあります. 今回は、その中でも特に自由度の高いNURBS曲線の基本的な事項をおさらいします.
ベクタ形式ではBezier曲線が有名で、古くから使われていますが、Bezier曲線にはいくつかの欠点があります. その中でも代表的なものを纏めてみると
- 次数が制御点数に影響される
- 重み付けができない
- 局所的な変更が難しい
これらの問題点を解決するために考案されたのが、BSpline曲線です.
NURBS曲線(Non-Uniform Rational BSpline curve)とは、名前の通りBSpline曲線に便利な特性(重み付けと非一様ノット列)を追加した形のことを言います.
NURBS曲線はBezier曲線とBspline曲線の上位版にあたり、高度な表現力をもちながら扱いやすい表現です
NURBS曲線の定義
NURBS曲線は以下の式で表現されます.
ただし、k
は制御点の個数, n
は次数です
はBSpline基底関数と呼ばれ、以下のように定義されます
のとき
のとき
ここで、のときに1を返す範囲は、
のどちらかを採用します.
開区間になっているほうの端は範囲に含まれなくなるので、エラー処理を行う必要があることに注意してください.
また、
を採用して、で例外処理を行う方法もあります
以下に基底関数の素直な実装を載せておきます.(改良の余地はあります.エラー処理はお好みで追加してください)
今度はDeBoorのアルゴリズムとかをやりたいですね
/** @fun * @brief BSpline基底関数を計算します * @param t 独立変数t * @param i 制御点の番号i * @param n 次数n * @param knots ノットベクトル */ double NURBS::basis(double t, size_t i, size_t n, const std::vector<double> &knots) { // N[i][0] if (n == 0) return (knots[i] <= t && t < knots[i + 1]) ? 1 : 0; double a1 = knots[i + n] - knots[i]; double a2 = knots[i + n + 1] - knots[i + 1]; double b1 = t - knots[i]; double b2 = knots[i + n + 1] - t; // "ゼロで割られたらゼロ"という決まりです double r1 = (b1 == 0) ? 0 : basis(t, i, n - 1, knots) * b1 / a1; double r2 = (b2 == 0) ? 0 : basis(t, i + 1, n - 1, knots) * b2 / a2; return r1 + r2; }
型情報の保存
あけましておめでとうございます.
突然ですが、基底クラスから元の型を復元したいと思うことはないでしょうか.C++ではそのような型情報の保存を簡単に行えるみたいです.
具体的には、仮想関数を利用して型情報を返す関数を定義します.
#include <iostream> #include <typeinfo> struct Base{ //型情報を返す仮想関数 virtual const std::type_info& type() const{ return typeid(*this); } }; struct ClassA : public Base{ }; int main(){ //基底クラス Base base; //派生クラス ClassA classA; //基底クラスのポインタに格納された派生クラス Base* base_classA = &classA; std::cout << "type of base is " << base.type().name() << std::endl; std::cout << "type of classA is " << classA.type().name() << std::endl; std::cout << "type of base_classA is " << base_classA->type().name() << std::endl; return 0; } /* 実行結果: type of base is 4Base type of classA is 6ClassA type of base_classA is 6ClassA */
このようにして、仮想関数を使うことで、基底クラスのポインタからも元の型名が取り出せます.
typeinfo
が致すればキャストするようにすれば、比較的安全に型が復元できるはずですね.
このテクニックはboostライブラリのboost::any
でも使用されているようです.
virtualなデストラクタ
C++では、ポリモーフィズムを利用するために作成した基底クラスのデストラクタにはvirtual
を付けることがあるようです.
virtual
がないと、基底クラスのポインタで管理している場合は、基底クラスのデストラクタのみが呼ばれてしまうためです.
初心者は「そんなこと聞いてないよ」となりそうで、不親切だとは思いますが、様々な理由でこうなっているはずなので仕方ないでしょう.
以下のようなコードで確認してみましょう
#include <iostream> #include <memory> struct Base{ Base(){ std::cout << " Base constructor" << std::endl; } ~Base(){ std::cout << " Base destructor" << std::endl; } }; struct Child : public Base{ Child(){ std::cout << " Child constructor" << std::endl; } ~Child(){ std::cout << " Child destructor" << std::endl; } }; int main(){ { std::cout << "unique_ptr:" << std::endl; std::unique_ptr<Base> pBase = std::make_unique<Child>(); } { std::cout << "shared_ptr" << std::endl; std::shared_ptr<Base> pBase = std::make_shared<Child>(); } std::cout << "Raw pointer" << std::endl; Base* pBaseRaw = new Child(); delete(pBaseRaw); return 0; }
実行結果はこうなります
unique_ptr: Base constructor Child constructor Base destructor shared_ptr Base constructor Child constructor Child destructor Base destructor Raw pointer Base constructor Child constructor Base destructor
Rawポインタとunique_ptr
では、Childのデストラクタが呼ばれていないのが確認できます.
基底クラスのデストラクタにvirtual
をつければ、全てChildのデストラクタを呼ぶようになります
また、shared_ptr
はvirtualデストラクタがないにもかかわらず、Childのデストラクタが呼ばれているのも面白いですね.
これは、shared_ptr
が作成時のクラスを利用してポインタを破棄してくれるからみたいです.
しかし、unique_ptr
ではやはりvirtual
が必要となるので、あまり安心はできなさそうですね.
単純な三角メッシュ
はてなのマークダウンの練習
CGの入門書に書いてあるようなやつです
メッシュ構造
メッシュ構造は3DCGで利用される3Dモデルの表現法です.
ここでは、シンプルなobjファイルで使われるような、頂点+三角形
で構成されるメッシュ構造を書いてみました
template <typename T> struct Vertex{ //頂点 //...コンストラクタなど...// tvec3<T> position; //位置座標 }; template <typename T> struct Face{ //面 //...コンストラクタなど...// std::shared_ptr<Vertex<T>> verticies[3]; //三頂点へのポインタ tvec3<T> normal; //法線 }; template <typename T> struct Mesh{ //メッシュ //...コンストラクタなど..// std::vector<std::shared_ptr<Vertex<T>>> verticies; //頂点へのポインタリスト std::vector<std::shared_ptr<Face<T>>> faces; //面へのポインタリスト };
各面はその三角形を構成する3頂点へのポインタを持ちます.
ついでに法線まで計算する気まんまんですが、不要な場合はなくても良いですね.
関数を全部削ってしまいましたが、簡単なので.
リソース開放はshared_ptr
とかに任せちゃうのがナウなヤングのやり方じゃないでしょうか.
もっと複雑なハーフエッジ構造などは少し凝った構築をしないといけなくて大変で、それに対する最適化手法も色々あります.
CGは奥が深いですね.
Fiio D03Kで遊ぶ
Fiio D03K
Fiio D03Kは安価なSPDIF入力のDACです.
入力は192KHz,24bitで光、同軸入力をサポートします.
どこかに192KHzは同軸のみと書いてありましたが、私の環境では光でも192KHz/24bitを受けれています.
オヤイデが代理店をやってるみたいです.高級オーディオの印象がありますが、こんなところで名前を見るとは思いませんでした.
D03KのIC構成は
CS8416 -> CS8416 -> LVM358
となっております.オペアンプが汎用品ですので、このあたりを変えて遊ぶことができます.
詳しい情報はこのページに載っています. よくできているので是非参考にしてみてください.
コンデンサとオペアンプの交換
回路の定数を変更しない簡単な強化を行ってみました.
電源部の470ufをOSコン(470uf)に変更.元のコンデンサはロープロファイル品なので、高さに注意してください.
私は頑張ってねじ込みました...
オペアンプはLVM358からOPA2353,OPA2211を試しました.
どちらも聴いた感じちゃんと動いているようですが、OPA2211では電源からノイズが乗りやすくなった気もします.
結局、音質面で高性能なOPA2211をチョイスしました. 動作電圧的にはギリギリかもしれません.
その他にも、AD8656やLME49721なんかも動くのかな?試してないですが.
パッケージはSOですのでMSOP版を買わないように注意しましょう.
5千円以内でこの音質が楽しめるなら十分コストパフォーマンスに優れると言えそうです.
本格的に電子工作に手を出すならオシロスコープがほしいものです.入門機でも結構いい値段するんですよね.
ではでは.