C++ プログラミング

Contents

Tips

packedされた構造体のメンバの参照によるバインド

構造体を packed 属性でぐしゃっとつぶして、alignment が崩れている (可能性がある)場合に、メンバ変数を引数を呼び出し先の 関数で、参照型でバインドしようとすると、コンパイラのエラーになる (g++-3.4.4, g++-4.1.1 で確認)。:

#include <iostream>

struct A {
      int     b;
} __attribute__ ((packed));

void func(int& val)
{
      std::cout << "val = " << val << std::endl;

      val *= 2;
}

int main()
{
      A a;

      a.b = 10;

      //
      // func(a.b);
      //
      // This code causes the compile error on g++-4.1.1.
      //
      //      error: cannot bind packed field 'a.A::b' to 'int&'
      //

      int& x = a.b; func(x);

      std::cout << "val = " << a.b << std::endl;
}

上のコードのコメント内の func(a.b) ではコンパイルが通らない。 で、int& x = a.b; func(x); として一旦呼び出し側で明示的に 一時的な参照型の変数に格納しておいてやる。これだとコンパイルが 通る。けど、これはコンパイラがやってくれても、いいじゃん。 Visual C++ はやってくれるよ。

signed/unsignedのミスマッチ

今更ながら、char, short, unsigned int, long longなどの整数型を比較 する時のsigned/unsignedのミスマッチが気になる。:

short us = -1;
unsigned int i = 1;

if (us < i) {
  std::cout << "Expected" << std::endl;
}
else {
  std::cout << "Unexpected" << std::endl;
}

これで、コンパイル時に warning が出たりするけれど、たとえば warning が 何百とでて(C++ではありがち)とりあえずほっといたりすると、痛い目に 合う。

constメンバ関数へのポインタ

メンバ関数はconstでオーバーロードできるので、メンバ関数へのポインタだって 別だけど、どうやって書くの? こう。:

#include <iostream>

class
TestClass
{
public:
        void print()
        {
                std::cout << "Non Const" << std::endl;
        }

        void print() const
        {
                std::cout << "Const" << std::endl;
        }
};

int
main()
{
        typedef void (TestClass::*memfun_t)();
        typedef void (TestClass::*memfun_const_t)() const;

        memfun_t memfun1 = &TestClass::print;
        memfun_const_t memfun2 = &TestClass::print;

        TestClass test;

        (test.*memfun1)();
        (test.*memfun2)();
}

例外処理

try-catch で例外をちゃんと受け取らないと、プログラムは abort してしまうので、

  • set_exception()
  • set_terminate()

とかいう関数でユーザ定義の処理にすることができる。これは libc の関数ではなく、C++ の言語仕様なので安心して 利用可能。

例外

まとめ:

exception <----- logic_error   <----- domain_error
                               <----- invalid_argument
                               <----- length_error
                               <----- out_of_range
          <----- runtime_error <----- range_error
                               <----- overflow_error

代入演算子 = のオーバーロード

代入演算子については、C++ の言語として他の算術演算子 * や == といった ものから区別され、大域関数ではオーバーロードできない。non static な メンバ関数でないといけない。

後置演算子 ++ 定義

ユーザ定義の後置演算子 ++ は、前置演算子 ++ と区別するために:

Object & operator ++ ()
Object   operator ++ (int)

として宣言される。int はダミーの引数なので使わない。 「プログラミング言語 C++」第3版 11.11参照。

new でできるオブジェクトの配置

class X; new X とかでオブジェクトの領域を確保して、初期化することは 基本中の基本だけど、あらかじめ割り当てておいた領域に新たなオブジェクト を作成することができる。 「プログラミング言語 C++」第3版 10.4.11参照。:

char buf[1024];

new (buf) X

構造体のポインタのソート

勉強不足の STL はメモメモ。int 型の vector<int> のソートは、 例がたくさん見つかったけど、任意の構造体へのポインタ(vector<T*>) の場合は以下の通り。:

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

template<typename T>
struct less_ptr: public ::std::binary_function<T *, T *, bool>
{
public:
        bool operator () (const T * lhs, const T * rhs)
        {
                return *lhs < *rhs;
        }
};

class A
{
        int     m_val;

public:
        A(int val): m_val(val) {}

        bool
        operator < (const A & rhs) const
        { return m_val < rhs.m_val; }

        int val() { return m_val; }
};

template<typename T>
bool
greater_ptr (T * lhs, T * rhs)
{
        return ! ((*lhs) < (*rhs));
}

int
main()
{
        typedef ::std::vector<A *> AList;

        AList alist;

        alist.push_back(new A(10));
        alist.push_back(new A(1));
        alist.push_back(new A(5));
        alist.push_back(new A(3));
        alist.push_back(new A(4));

        // 関数ポインタアダプタを使ったソート
        //
        std::sort(alist.begin(), alist.end(),
        std::ptr_fun(&greater_ptr<A>));

        for (AList::iterator i = alist.begin(); i < alist.end(); ++i) {
                std::cout << (**i).val() << " ";
        }
        std::cout << std::endl;

        // 便利テンプレートをつくってみた
        //
        std::sort(alist.begin(), alist.end(), less_ptr<A>() );

        for (AList::iterator i = alist.begin(); i < alist.end(); ++i) {
                std::cout << (**i).val() << " ";
        }
        std::cout << std::endl;
}

二重字とテンプレート引数

二重字というのは、&&(and)、|(bitor)などの記号の別の表現方法。 (「プログラミング言語 C++」第3版 C.3.1参照)。ASCII の特殊文字の 位置に、キャラクタセットが割り当てられている場合の回避策として つかわれる。例えば、'&'は "<:"、'|'は":>"に置き換えられる。

これが以下のコンパイルエラーを引き起こす。:

template<
        template <class> class ThreadingModel
        >
class MultithredingClass
{
        ...
}

MultithreadingClass<::Loki::ClassLevelLockable> object;

<: が二重字として解釈され、意味不明のエラー出力を頂戴する。 最後の行を以下に変更。:

MultithreadingClass< ::Loki::ClassLevelLockable> object;

のようにスペースを一個いれておく。型名をマクロの引数に する場合にも気を付ける。

コンストラクタ関数テンプレート

コンストラクタも一応テンプレートにできる模様(g++-2.95.3, g++-3.3)。 あたりまえと言えばあたりまえなのか?

任意のクラスを継承するときには、Class B みたいにパラメータの数だけ コンストラクタを書いておくと便利かも。:

#include <iostream>
#include <string>

class A {
public:
        A() {}
        A(const std::string & str): m_str(str), m_int(0) {}
        A(const std::string & str, const int n): m_str(str), m_int(n) {}

        void print_str () { std::cout << m_str << std::endl; }
        void print_int () { std::cout << m_int << std::endl; }

private:
        const std::string m_str;
        const int m_int;
};

template<typename Base>
class B: public Base
{
public:
        B () {}

        template<typename P1>
        B (P1 p1): A(p1) {}

        template<typename P1, typename P2>
        B (P1 p1, P2 p2): A(p1, p2) {}
};

int
main()
{
        B<A> *b1 = new B<A>("Hello, world!!");
        B<A> *b2 = new B<A>("Good-bye world!!", 10);

        b1->print_str();
        b2->print_int();
}

typeid() による動的型情報

仮想関数のない継承関係の場合は、"4base" と表示される。:

#include <iostream>
#include <typeinfo>

using namespace std;

class base
{
public:
//      virtual ~base() {}
};

class derived: public base
{
};

int
main()
{
        base * p = new derived;

        cout << typeid(*p).name() << endl;
}

ところが、仮想関数を実装すると、"7derived" と表示される。:

#include <iostream>
#include <typeinfo>

using namespace std;

class base
{
public:
        virtual ~base() {}
};

class derived: public base
{
};

int
main()
{
        base * p = new derived;

        cout << typeid(*p).name() << endl;
}

限定子としての template

「プログラミング言語C++ 第3版」の C.13.6 「限定子としてのtemplate」に 書いてあったことなんだけど、はまりまくったのでメモ。

以下のコード "T::print<U>()" の部分がコンパイルエラー。なぜならば コンパイラは T::print < U と解釈して、「小なり」演算子と勘違いする。 なので、コンパイラに「print は template なんだよ」と教えてやらなければ ならない。typename っていうのはあるが、template を限定子として使うとは しらなかった。Loki のソースは読むべし。

なので、"T::template print<U>()" に書き直せばよい。:

#include <iostream>
#include <string>
#include <typeinfo>

using namespace std;

class PrintCout
{
public:
        template<typename T>
        void print()
        {
                cout << typeid(T).name() << endl;
        }
};

class PrintCerr
{
public:
        template<typename T>
        void print()
        {
                cerr << typeid(T).name() << endl;
        }
};

template<typename T>
class PrintTypename
{
public:
        template<typename U>
        void print()
        {
                T::print<U>();
        }
};

int
main()
{
        PrintTypename<PrintCout> obj;

        obj.print<int>();
        obj.print<char>();
        obj.print<double>();
}

以下、書き直したソース:

#include <iostream>
#include <string>
#include <typeinfo>

using namespace std;

class PrintCout
{
public:
        template<typename T>
        static void print()
        {
                cout << typeid(T).name() << endl;
        }
};

class PrintCerr
{
public:
        template<typename T>
        static void print()
        {
                cerr << typeid(T).name() << endl;
        }
};

template<typename T>
class PrintTypename
{
public:
        template<typename U>
        void print()
        {
                T::template print<U>();
        }
};

int
main()
{
        PrintTypename<PrintCout> obj;

        obj.print<int>();
        obj.print<char>();
        obj.print<double>();
}