モダン C++ プログラミング

モダン C++ プログラミング

このドキュメントはサイボウズ社内のトレーニング用に作成したものです。
作成時点では C++11 はまだあまり利用できない状況でしたので、C++98 ベースの記述になっています。
いずれ更新を予定しています。 

モダンの定義

モダンとはテンプレートメタプログラミング(TMP)を駆使することです。嘘です。

宗教論争に意味はないので、ここでは
「最近の C++ の仕様・機能を理解し、C より実装効率が良く不具合の少ない」
プログラミング技法を「モダン C++ プログラミング」と定義します。

つまり、不具合が少なく、かつ C にはもう戻れなくなるような効率の良さを達成するものです。
学習効率(ROI)が極めて良くないような技法(例えば TMP)は、この定義では除外されます。

勉強方法

お勧めの順序は以下。決して全部を読もうとしないこと。

  1. C++ Language Tutorial のような、あっさりしたもので基本的な書き方を学ぶ
  2. Effective C++ を流し読みする。第5章まででよいです。
    # つまり継承やテンプレートプログラミングは飛ばしていい
  3. C++ Primer を脇に置いてコーディングする
  4. それでも分からないときはこのドキュメントやエキスパートに頼る

その他リファレンス

  • cplusplus.com
    STL のリファレンスが便利
  • Cppreference
    C++11 にも対応したリファレンス。C++11 でどうなるのか知りたければこちら。

これで十分、いいコードが書けるようになります。

コーディングスタイル

絶対にやったほうがいいことだけまとめておきます。

const はきちんとつける

C++の価値の 20% くらいは const にあります。
const は安全なプログラムを作る上で不可欠な機能ですが、使い方を知らないとコンパイラに怒られまくって嫌になります。
正しい使い方を学びましょう。

const を使わないダメなコード
#define BLOCK_SIZE 4096

void print(std::string& s);

class C {
public:
    C(int bufsiz) {
        buffer_size = bufsiz;
    }

    int size() { return buffer_size; } // const メソッドにするべき

private:
    int buffer_size; // const にすべき
};
良い例
const int BLOCK_SIZE = 4096;

void print(const std::string& s);

class C {
public:
    C(int bufsiz): buffer_size(bufsiz) {} // const メンバーは初期化子で初期化

    int size() const { return buffer_size; } // const の位置に注意

private:
    const int buffer_size;
};

const なポインタ

例えばバッファの先頭位置を必ず差すポインタはどう書くか?

void f( const std::string& s ) {
    const char* p = s.c_str();    // const?
    printf("hoge %s", p);

    p = "fuga";  // 代入できるので const じゃない。
}

正しくはこう。

void f( const std::string& s ) {
    const char* const p  = s.c_str(); // p は代入不可
    ...
}

const な STL コンテナ

const な STL コンテナ(vector 等)をイテレートするには通常の iterator じゃなく、const_iterator を使います。
iterator には逆順(reverse_iterator)もありますが、そちらも const の場合は const_reverse_iterator を使います。

void f( const std::vector<std::string>& v ) {
    for( std::vector<std::string>::const_iterator it = v.begin();
         it != v.end(); ++it ) {
        ...
    }

    // for( const std::vector<std::string>::iterator it = v.begin();
    // とかするのは間違い。iterator 自体は const じゃないので
}

explicit をつける

一引数をとるコンストラクタは、暗黙的な型変換に使われて思いもよらないことが起こることがあります。
暗黙の型変換を抑止するために、一引数のみのコンストラクタには explicit を指定してください。
C++ の設計ミス(デフォルトを explicit にするべきだった)の一つでしょう。

class C {
public:
    C( int i ) { ... }              // int が暗黙で C に変換される可能性がある !
    explicit C( int i ) { ... }     // explicit をつければ、暗黙の型変換は防止できる
    C() { ... }                     // デフォルトコンストラクタにはつけなくていい
    C( int i, long l ) { ... }      // 二引数以上でもつけなくていい
private:
    C( char c ) { ... }             // もちろん private なら explicit しなくていい
};

初期化・初期化子・定義

コンストラクタには、お作法があります。以下は鉄則として考えておきましょう。

  1. primitive は明示的に初期化する
    スタックオブジェクトでは(2013-12-18 21:32 訂正、newでも同様と指摘を頂きました)
    int 等の primitive 型のメンバーは初期化されません。
    明示的に初期化をしないと、思わぬ事故につながるので、必ず初期化しましょう。

    struct C {
        C() {}                  // i は 0 初期化されない!
        int i;
    };
    
    int f() {
        C c;
        return c.i;             // 不定
    }
    
  2. 初期化子で必ず全メンバーを初期化する
    初期化はコンストラクタのボディでいいと思っていませんか?
    初期化子をかかなくても、じつはデフォルトコンストラクタでメンバーは初期化されています。
    また、const や参照メンバーは初期化子でなければ初期化できません。必ず初期化子で全メンバーを初期化しましょう。

    class C {
    public:
        C(const std::string& s) {
            m_name = s;              // 初期化でなく代入。もし m_name が const ならコンパイルできない。
            m_i = 0;                 // コンパイルエラー
        }
        C(const std::string& s):
            m_name(s), m_i(0) {}     // good
    private:
        std::string m_name;
        const int m_i;
    };
    
  3. 初期化子の記述順序は、メンバーの定義順にする
    実は初期化子の記述順序は、初期化の順序と無関係です。メンバーは、メンバーが定義された順に初期化されます。
    なので、メンバーの定義順と異なる初期化子の記述順序はバグのもとになります。

    class C {
    public:
        C( std::size_t size):
            m_buf(size),
            m_top(&(m_buf[0])),
            m_bottom(m_top + size) {}   // m_bottom の定義が先なので、m_top 初期化前にこちらが実行される
    private:
        std::vector<char> m_buf;
        char* const m_bottom;
        char* const m_top;
    };
    
  4. コンストラクタのボディは原則として空
    整合性のチェックなどをのぞき、複雑なロジックを避けるべきです。なぜかというと、C++ は Java と違ってオーバーロードされた他のコンストラクタに初期化処理を委譲できないため、複数のコンストラクタで同じ処理を書くことになりがちだからです(後述する C++11 では可能になります)。

デストラクタと継承

継承されるクラスのデストラクタには virtual つけないといけません。
継承の可能性があるクラス(シングルトンなどは継承されないでしょう)では、virtual つけるのを原則としましょう。
つけないと、親クラスにアップキャストされたオブジェクトのデストラクタが正常に走りません。

親クラスのデストラクタは自動的に呼び出されるので、サブクラスのデストラクタ内で明示的に呼び出す必要はありません。

class A {
public:
    A() {}
    virtual ~A() {}       // 処理が空でも virtual つける
};

class B {
public:
    B() {}
    ~B() {
        // no need to call ~A().
    }
};

C++ で Java のような言語にあるインタフェースが欲しい場合、通常は純粋仮想クラスとして実装します。

危険な例
struct Interface {
    virtual void foo() = 0;
    virtual int bar(const std::string& s) = 0;
};

ところが純粋仮想クラスはあくまでクラスであってインタフェースではないため、このようなときも仮想デストラクタを定義するべきです。

正しい例
struct Interface {
    virtual ~Interface() {}
    virtual void foo() = 0;
    virtual int bar(const std::string& s) = 0;
};

名前空間を適切に使う

覚えることは四つ。

  1. 大域スコープのシンボルは作らない(必ず名前空間を使う)
  2. ヘッダでは using 禁止
  3. cpp ファイルでは無名名前空間でローカルシンボルをくくる
    C では static でしたが、C++ では無名名前空間を使うのがスマートです。

    const int CONSTANT = 3;     // const はデフォルトでファイルローカルスコープ
    
    namespace {          // 無名の名前空間
    
    void do_something() {       // 無名名前空間の名前は他のコンパイル単位からは参照不可能
    }
    
    }
    
    int main(int argc, char** argv) {  // main は見えるようにする
      do_something();
      return 0;
    }
    
  4. C ライブラリも std 名前空間へのラッパーを極力使う
    c.f. http://www.cplusplus.com/reference/clibrary/

    #include <cstddef>  // std::size_t など
    
    #include <cstdlib>  // std::malloc, std::free など

    ※2013-12-27 std::errno は存在しないので修正しました

警告は必ずなくす

C++ 以外にも言えることですが、コンパイラはの警告は 0 にしておきましょう。放置すると、深刻なものも見逃します。
g++ なら -Wall -Wnon-virtual-dtor -Woverloaded-virtual をつけるのを推奨します。

コピーと参照をしっかり使い分ける

Java などのオブジェクトと異なる C++ 一番の注意点(利点でもある)は、ヒープオブジェクトスタックオブジェクトの二種類が存在することです。ヒープオブジェクトは new で確保したオブジェクトのことで、これに関して注意することは後で解説します。ここではスタックオブジェクトに関して注意しておきます。

スタックオブジェクトとは、new で確保しないオブジェクトで、存在するスコープをすぎるとすぐにデストラクタが呼ばれて使えなくなります(これを利用するテクニックが後述する RAII です)。スタックオブジェクトの生存期間を超えたら参照は無効化されるので、そのような場合はコピーする必要があります。

やばげな例
class A {
public:
    A(const std::string& s): m_s(s) {}
    void print() const {
        std::cout << m_s << std::endl;
    }
private:
    const std::string& m_s;  // 参照を保持
};

void foo() {
    A a("aaa");  // "aaa" が暗黙的に std::string になるが、
    a.print();   // 一時オブジェクトなのでここでは既に無効
}

これをコピーするようにするのは簡単で、メンバーの m_s を参照でなくせば良いです。

動く例
class A {
public:
    A(const std::string& s): m_s(s) {}
    void print() const {
        std::cout << m_s << std::endl;
    }
private:
    const std::string m_s;    // コピーする
};

void foo() {
    A a("aaa");
    a.print();                // OK
}

そこでコピーだから安全と参照をやめていくと、コピー過多で遅いコードになります。
先の例でコピーをせずに動かすには、一時オブジェクトの生存スコープを少し延ばします。

コピーせずに動く例
class A {
public:
    A(const std::string& s): m_s(s) {}
    void print() const {
        std::cout << m_s << std::endl;
    }
private:
    const std::string& m_s;  // 参照を保持
};

void foo() {
    std::string t = "aaa";
    A a(t);
    a.print();               // OK
}

スコープが明確にできない場合は後述する参照カウント式のスマートポインタ(boost::shared_ptr 等)を使います。スタックオブジェクトを大量に使うとスタックオーバーフローの危険もありますので、大量で長期間生存するオブジェクトはオブジェクトプール等での管理も検討しましょう。

一時オブジェクトの生存期間

上記で「一時オブジェクトはスコープをすぎるとすぐにデストラクタが呼ばれて使えなくなる」と言及しましたが、C++ ではそのスコープは一時オブジェクトが含まれる式全体(full-expression)と定義されています。なので、以下のコードは大丈夫です。

std::string foo() {
    std::string s = "/etc/fstab";
    return s;
}

void foo() {
    int fd = open( foo().c_str(), O_RDONLY );
    ...
}

foo() が返す一時オブジェクトである string から c_str()const char* なポインタを open(2) に渡しているわけですが、ポインタを作ったあとも一時オブジェクトの string は open の呼び出しが完了するまで生き続けます。

c.f.

文字列

std::string のススメ

モダン C++ では C 文字列の代わりに std::string が使われます。
大事なことだからもう一度。

モダン C++ では C 文字列の代わりに std::string が使われます。

例えば以下のような C のコードは、

void f(const char* s) { ... }
void g(const char* s2) {
    char buf[256];
    snprintf(buf, sizeof(buf), "Hello %s", s2);
    f(buf);
}

std::string を使う C++ なら以下のように書けます。

void f(const std::string& s) { ... }
void g(const std::string& s2) {
    f("Hello " + s2);
}

文字列比較も == などで書けます。strcmp はさようなら。

bool is_hello(const std::string& s) {
    return s == "Hello";
}

C 文字列(const char*)は const std::string への暗黙の型変換が用意されているので、const std::string を渡す場面でも C 文字列を使うことができます。

void f(const std::string& s) { ... }
void g() {
    f("Hello world!");    // 暗黙的に std::string に変換される
}

std::string が使えない数少ないケースとして fstream ライブラリでは、ファイル名に C 文字列しか受け付けません。これは STL が整備される前の古い時代の名残で、後述する C++11 ではstd::stringでも大丈夫になりました。

void f(const std::string& s) {
    std::ofstream out(s.c_str());  // C 文字列にしないといけない
}

std::string は可変長のバッファとしても利用できます。その勢いで char 配列としても扱いたいところですが、C++03 規格では string の内部バッファが連続メモリであることは保証していないため、以下のようなコードは動作が保証されていません。C++11 では保証されています。

std::string buf;
buf.reserve(256);           // 256 byte 確保
char* p = &(buf[0]);        // コンパイルは通るし動作もするが、連続の保証はない
char* p = buf.data();       // 連続メモリだが const つきなのでコンパイルエラーになる

cybozu::String

https://github.com/herumi/cybozulib

@herumi 謹製の、ユニコードを扱える std::string 互換クラスライブラリ。UTF-8 の文字列を便利に使えます。

テストコードをみればだいたいわかるはず。

例外

新規に書くコードにおいては、例外は使うべきものです。C のように関数の返り値をいちいちチェックするコードを省け、後述する RAII テクニックによって一時ファイル等のゴミを後片付けしながらきちんとエラー処理をすることができるからです。

ただ、初期の C++ では例外がなかったため、Google 等一部の例外安全でないコード資産が多くある会社では例外を使えないところもあります。

このドキュメントはモダン C++ を前提にしているので、今日すでに一般的に実装されている例外を活用します。

例外クラス

C++ ではコピー可能なものなら何でも throw することができますが、モダン C++ では原則として std::exception の派生クラスを throw します。標準例外クラスは stdexcept に用意されているので、適切に利用します。

プログラミングエラー系は std::logic_error とその標準派生クラス(std::invalid_argument 等)を使えばほとんど十分でしょう。しょせんプログラミングエラーなので、直せば済むことだから。

一方で、正しくプログラミングしても避けられない実行時のエラーは、その原因別に std::runtime_error の派生クラスを作るべきでしょう。

class protocol_error: public std::runtime_error {
public:
    explicit invalid_packet(const std::string& s):    // explicit 付け忘れないように
        std::runtime_error(s) {}
};

void f(const std::string& s) {
    if( s.empty() )
        throw std::invalid_argument("s must not be empty.")'
    Socket s(...);
    s.send(s);
    std::string reply = s.recv();
    if( reply != "ok" )
        throw protocol_error("not ok.");
}

例外の投げ方、受け取り方

例外はスタックオブジェクトを throw し、その const 参照で catch するのが原則です。
このスタックオブジェクトはランタイムの専用の領域にコピーされるので、コピー可能でなければなりません。
まあ上記のようなクラスはコピー可能なので大丈夫です。

try {
    ...
    throw std::runtime_error("hoge!");      // スタックオブジェクト
    ...
} catch( const std::runtime_error& e ) {    // const 参照
    std::cerr << e.what() << std::endl;
}

例外は必ず catch すること

catch されなかった例外は最終的に std::terminate で処理されますが、その場合一切のスタックオブジェクトが破棄されません。結果として、後述する RAII を利用したクリーンアップ処理(ゴミファイルの消去など)が動作しないといった不都合が生じる可能性があります。(c.f. http://ymmt.hatenablog.com/entry/2012/02/01/015040)

典型的な C++ の main 関数は以下のようになるでしょう。

int main(int argc, char** argv) {
    try {
        // do something
        return 0;
    } catch( const std::exception& e ) {   // 標準的な例外オブジェクト
        // exception handling
    } catch( ... ) {                       // その他の例外 ... はすべてをキャッチする構文
        // exception handling
        throw;                             // catch して rethrow すれば、main 以降のスタックは処理できる
                                           // rethrow しないと POSIX スレッドプログラムは abort するので注意。
    }

    return 1;
}

デストラクタでは例外は投げないこと

デストラクタから例外を投げること自体は違法ではないものの、デストラクタが例外を投げると例外中に例外が発生する可能性が著しく高まります。この二重例外は違法で、std::terminate() が呼ばれてプログラムが異常終了してしまうことになります。

というわけで、以下の方針に従ってください。

  1. 可能な限りデストラクタは何もしないこと
    後述するスマートポインタや STL コンテナを使う限り、delete を呼ぶことはなくなります。
  2. エラーは握りつぶすこと
    例えば close(2) が失敗しても、例外を投げたりしてはいけません。
    ログに記録することでさえ危険なので、無視していいものは無視しましょう。

    class C {
    public:
        C(const char* filename): m_fd(open(filename, O_RDONLY)) {
            if( m_fd == -1 )
                throw std::runtime_error("open failed.");
        }
        ~C() {
            if( m_fd != -1 )
                close(m_fd);    // close が失敗しても気にしない
        }
    private:
        const int m_fd;
    };
    

後述するルールに従っていれば、コンストラクタでは例外を投げて良いです。
むしろ、投げないのが無理というものです。STLコンテナを使うだけで std::bad_alloc が飛ぶ可能性があるので。

コンストラクタで例外を投げたときに起こるのは以下のようなことです。

  1. 初期化済みのメンバーのデストラクタを呼ぶ(初期化前のものは呼ばれません)
  2. もしオブジェクトが new で確保されたものであれば、確保したメモリを解放する
  3. 例外を呼び出し側に propagate する

c.f.

例外指定は使わないこと

Java と違い、例外指定がオプションでかつ実行時チェックである C++ では、例外指定は全く無意味であると結論されています。

C++11 では例外指定は deprecate され、例外を絶対に投げないことを保証する noexcept キーワードが追加されました。コンパイラの最適化用だと考えてください。

例外の賢い使い方

Java の例外は checked exceptions と呼ばれ、メソッドシグネチャで発生する可能性がある例外を明示する必要があります。
これはなかなかの難物で、多数の例外の列挙を防ぐために Java では一度 catch して別の例外に wrap して投げなおすというコードが多用されます。

C++ の例外は checked exceptions ではないので、情報を付加する目的がないのであれば、wrap するような面倒なことは避けた方が良いでしょう。
詳しくは以下の資料に目を通してください。

例外安全

例外安全とは、例外が発生した後もプログラムが安全に走れる保証のことです。例外安全というと、例外を使わなければ関係ないように聞こえますが、実際にはリソースリークなどを確実に防止するので、長時間走るネットワークサーバーやデーモンでも非常に重要な要素となります。

結論からいえば、例外安全なプログラムは以下のルールに従えば作ることができます。

  1. STLコンテナを使い、極力 new しない
  2. new したポインタはスマートポインタに格納する
  3. メモリ以外のリソースは RAII 管理クラスで管理する
  4. オブジェクトのコピーと代入を制御する

このルールを守ればほとんどの場合例外を安全に使うことができるでしょう。

マルチスレッドプログラム等、現在のスタック(スレッド)以外と協調動作するプログラムでは特別な注意が必要になることがあります。
後日マルチスレッドプログラミングの回で解説します。

RAII

RAII (Resource Acquisition Is Initialization) はスタックオブジェクトがそのスコープを離れると自動的にデストラクタが呼ばれ解放されることを利用した、リソース管理のテクニックです。
Java や Python の finally を、わざわざ書かなくても実行してくれるものと考えてください。

RAIIでファイルを管理するクラス
class file {
public:
    file(const std::string& filename):
        m_fd(open(filename.c_str(), O_RDONLY)) {
        if( m_fd == -1 )
            throw std::runtime_error("failed to open" + filename);
    }
    virtual ~file() {
        if( m_fd != -1 ) close(m_fd);
    }
    int fileno() const {
        return m_fd;
    }
    int read(char* in, std::size_t len) {
        std::size_t n_read = 0;
        while( n_read != len ) {
            int t = read(m_fd, in, len - n_read);
            if( t == 0 )
                throw std::runtime_error("unexpected end of file");
            if( t == -1 )
                throw std::runtime_error("failed to read");
            n_read += t;
        }
    }
private:
    const int m_fd;
};

void read(const std::string& filename) {
    file f(filename);
    std::vector<char> v(4096);
    f.read(&(v[0]), 4096);         // ファイルが 4096 バイト未満なら例外が飛ぶが、f は close される
    // 正常なときも f は close される
}

RAII は通常の実行時はもちろん、例外が発生した時にもきちんと実行されるので、例外安全なプログラムを書くためのキーとなるテクニックです。STLコンテナやスマートポインタは RAII を活用するライブラリです。

コピー制御

C++ はオブジェクトをコピーや代入するために、通常は単にメモリの値をコピーするコピーコンストラクタや代入オペレーターを自動的に生成してくれます。
ですが、メモリ以外のリソース、例えばロックやファイルはコピーしにくいものです。

以下の方針に従ってコピー・代入を制御してください。

  1. メモリ上の構造は極力 STL コンテナ(とそのコピー・代入)を使う
  2. ファイルディスクリプタや同期オブジェクト(mutex 等)があれば、コピー・代入を禁止する

コピー、代入を禁止するには、コピーコンストラクタと代入オペレーターを private 宣言します。
private にアクセスできるメソッドや friend 関数にも禁止するために、宣言のみで定義はしません。

class NonCopyable {
public:
    NonCopyable() {}
    virtual ~NonCopyable() {}
private:
    mutable mutex_t m_lock;    // mutex は普通コピーできない

    NonCopyable(const NonCopyable& );            // コピーコンストラクタ
    NonCopyable& operator=(const NonCopyable& ); // 代入オペレータ
};

Boost には boost::noncopyable というクラスがあり、これを private 継承することで簡単にコピー・代入を禁止できます。
また C++11 ではデフォルトのコピーコンストラクタや代入オペレータの生成を禁止することができるようになりました。

C++11での例
class NonCopyable {
public:
    NonCopyable(const NonCopyable&) = delete;             // デフォルト生成を禁止
    NonCopyable& operator=(const NonCopyable&) = delete;  // 同上
...
};

なお、コピーができないオブジェクトを共有するためには、後述する参照カウント式のスマートポインタを利用してください。

スマートポインタの使い方

スマートポインタとは、RAII を使って適切に delete してくれるクラスです。
オペレーターオーバーロードのお陰で、通常のポインタとあまり違いを意識せずに使うことができます。

スマートポインタを使うことで、delete 忘れを防止できます。
特にコンストラクタで二つ以上のオブジェクトを new するような場合に、スマートポインタに入れることで途中の例外によるメモリリークを防止することができます。

メモリリークしてしまう例
class D {...};

class C {
public:
    C(): m_obj1(new D), m_obj2(new D) {}   // m_obj2 のコンストラクタで例外が起きると
                                           // m_obj1 がリークしてしまう
    virtual ~C() {                         // デストラクタも自分で書かないといけない。
                                           // コンストラクタで例外が発生すると呼ばれないし。
        delete m_obj1;
        delete m_obj2;
    }
private:
    D* const m_obj1;
    D* const m_obj2;
};

このようなときは std::auto_ptr を利用することでうまく解決ができます。

スマートポインタで解決
class C {
public:
    C(): m_obj1(new D), m_obj2(new D) {}   // m_obj2 のコンストラクタで例外が起きても
                                           // m_obj1 は適切に delete される
    // デストラクタは不要
private:
    std::auto_ptr<D> m_obj1;
    std::auto_ptr<D> m_obj2;
};

簡単ですね! ただ、std::auto_ptr には以下のような制限があります。

  • C 配列には利用できない(delete[] してくれないため)
    C++11 で導入される std::unique_ptr ではできます。C++03 では std::vector を利用しましょう。
  • コピーできない
    コピーできないため、STLコンテナに入れてはいけません。
    STLコンテナに入れるには、後述する参照カウント式のスマートポインタを利用します。

C++11 では std::auto_ptr は deprecate され、std::unique_ptr を使うことになりました。
std::unique_ptr では C 配列も扱えますし、STL コンテナにも入れられます。

std::unique_ptr<char[]> buf( new char[256] );   // char[] の unique_ptr は delete[] される

std::auto_ptr では制限が厳しいため、boost や POCO や C++11 では参照カウント方式でコピーが可能なスマートポインタが用意されています。
例えば C++11 では std::shared_ptr として利用することができます。
参照カウント方式なので、循環参照を作るとメモリリークすることには注意してください。

void f(std::vector<Poco::SharedPtr<file> >& v) {
    Poco::SharedPtr<file> p( new file("hoge.txt") );
    v.push_back(p);      // コピー可能なので STL コンテナに入れて良い
}

STLコンテナの使い方

STL は今日の C++ コンパイラでは広く実装・最適化されています。
STLの中でも std::stringstd::vector といった STL コンテナはメモリ資源を RAII で適切に管理してくれる有用なものです。
STLコンテナを使うことで自分で new, delete する場面は著しく減らせるでしょう。

ただし、STLコンテナにはいくつか癖があります。

  • コピー・代入可能なオブジェクトしか格納できない
    例えば const なメンバーを持っているオブジェクトは代入できないため、格納できません。
    また、auto_ptr はコピー可能でないため格納できません。
    これらを格納する場合は、参照カウント式のスマートポインタを利用してください。
  • メモリ連続性の保証がされていないものがあります

C++11 では move という操作が規定されたため、コピー・代入可能でなくとも move 可能なら STL コンテナに格納できます。
先述した std::unique_ptrmove 可能なオブジェクトの一例です。

例えば vector は連続性が保証されているので、以下のような使い方ができます。
先述したように、string は C++03 では保証されていません。

int readfile(int fd) {
    std::vector<char> buf(256);
    char* const pBuf = &(buf[0]);
    return read(fd, pBuf, 256);
}

STLコンテナは内容を適切にコピーしてくれるので、例えば std::stringstd::vector を参照をつけずに渡せます。
ですが、メモリコピーが発生するので、大きなコンテナは参照で渡すようにしましょう。

無駄なコピー
std::string format(std::vector<std::string> v) {   // ここでコピーが発生
    std::string t;
    for( std::vector<std::string>::const_iterator it = v.begin();
         it != v.end(); ++it ) {
        t += *it;
        t += ", ";
    }
    return t;
}

int main(int argc, char** argv) {
    std::vector<std::string> v(argc);
    for( int i = 0; i < argc; i++ ) {
        v[i] = argv[i];
    }
    std::string s = format(v);    // ここでコピーが発生
    std::cout << s << std::endl;
    return 0;
}
参照を適切に使う
void format(const std::vector<std::string>& v,  // const 参照
            std::string& s) {                   // 参照
    s.clear(); // 空にする
    for( std::vector<std::string>::const_iterator it = v.begin();
         it != v.end(); ++it ) {
        s += *it;
        s += ", ";
    }
}

int main(int argc, char** argv) {
    std::vector<std::string> v(argc);
    for( int i = 0; i < argc; i++ ) {
        v[i] = argv[i];
    }

    std::string s;
    format(v, s);
    std::cout << s << std::endl;
    return 0;
}

テンプレートとの正しいつきあい方

テンプレートは便利な機能ですが、その文法や制限を学ぶのには高いコストがかかります。
このコストはメンテナンスをするチーム全体にかかるものなので、高度なテンプレートライブラリは自作しないようにしましょう。

具体的な指針としては、特殊化をしだすと途端に学習コストが高まります。
特殊化をする前に、オーバーロードで片付かないか検討しましょう。

モダン C++ ライブラリ

C++ は仕様が変遷してきたため、ライブラリもその時々のスタイルに合わせて作られています。
ここでは、このドキュメントで定義するモダン C++ に合致するライブラリを紹介します。
具体的には以下の条件を満たすライブラリです。

  • 例外安全
  • STL の積極的な利用
  • マルチスレッドセーフ

POCO

POCO はネットワークアプリケーションを作成するのに必要な機能を揃えたライブラリです。
SSLを含むネットワーク機能に加え、ファイルシステム・スレッド・時刻・ロギング・設定ファイルなどの機能があります。

テンプレートを多用してはいないため、ソースコードはシンプルで読みやすく、コンパイル速度も速いです。

Boost

Boost はテンプレートメタプログラミングを中心とした、非常に高度な C++ の技術を駆使したライブラリです。
C++11 にも多数の仕様が取り込まれる等、広く影響力を持ってはいますが、機能セットとしては POCO と大差がありません。

Boost のプログラムソースは初心者にはとても読めるものではなく、またテンプレートを駆使するためコンパイル速度が非常に遅いです。
特に有用な機能は C++11 に取り込まれていますので、Boost を使うことはお勧めしません。

QT and wxWidgets

QT は GUI アプリケーションの作成で広く使われている C++ のフレームワークです。
ただし、古くからあるため例外安全ではありません

wxWidgets も以前から使われている GUI ライブラリですが、native GUI ではないのでかっこ悪いのが残念。
例外は使わないが、例外安全ではあります。

その他

この辺参照。

C++11 について

C++11 は 2011 年に規格が標準化された新しい C++ の仕様です。とはいっても相当以前から規格が検討されていたこともあり、その仕様のかなりは Visual C++ 2010 や gcc-4.6 で利用することができます。

このドキュメントでも C++11 に関して色々と言及してきましたが、その他の特徴として以下のようなものがあります。

  1. auto による型推論
  2. 拡張された for
  3. move と右辺値参照
  4. static_assert
  5. スレッドライブラリ
  6. 正規表現

詳しくは Wikipedia 等を参考にしてください。