Boost.勉強会 #9 つくば ( 2012-05-26 )

C++ TIPS 4 CV修飾編
概要
 主に cppll ML でご紹介してきた tips をC++
  の仕様をより掘り下げた形でまとめ直し
  てみました。
 今回はcv修飾にフォーカスした内容です。



              C++ Tips Boost.勉強会 #9 つくば   2
概要
 cv修飾はC++のすばらしい機能のひとつで
 はあるのですが、細部は残念ところが
 多々あり、注意が必要なのでしっかり復
 習しておきましょう。



          C++ Tips Boost.勉強会 #9 つくば   3
C++ Tips

CV修飾
cv修飾ってなに?
 const 修飾と volatile 修飾を合わせて cv 修
 飾( cv-qualifiers )と呼ばれます。
  constexpr はcv 修飾とは関係ありません。ヤツ
   は 全然別物です。
  cv 修飾は型とメンバー関数に対して付けるこ
   とができます。
  元の型とは違う型と見なされ、オーバーロー
   ドの対象となります。
               C++ Tips Boost.勉強会 #9 つくば   5
const
 const 修飾されたオブジェクトはJIS 的には
  定値オブジェクトと呼ばれます。
 const は厳密には二段階の意味があります。
  一つ目は読み込み専用(readonly)であること。
  二つ目は不変(immutable)であること。



             C++ Tips Boost.勉強会 #9 つくば   6
const
 読み込み専用(readonly)であること。
  const なオブジェクトは書き換えられません。




            C++ Tips Boost.勉強会 #9 つくば   7
const
 不変(immutable)であること。
  生成時から const として作成されたオブジェク
   トは変更できません。
  この場合、コンパイラはコンパイル時にオブ
   ジェクトを作ってしまうことができます。ま
   た、そういうオブジェクトをconst_castなどで
   const外しを行い書き換えようとすると未定義
   動作となります。
             C++ Tips Boost.勉強会 #9 つくば   8
const
 const 参照あるいはconstなオブジェクトを
 指し示すポインタは、その指し示すオブ
 ジェクトはreadonlyであるがimmutableで
 あるとは限りません。



            C++ Tips Boost.勉強会 #9 つくば   9
const
 readonlyだがimmutableではない例:
   int a = 0;
   const int & b = a;
   printf("%dn", b); // 0
   a = 1;
   printf("%dn", b); // 1
   b = 2;             // NG

                  C++ Tips Boost.勉強会 #9 つくば   10
volatile
 volatile 修飾されたオブジェクトはJIS 的に
  は揮発性オブジェクトと呼ばれます。
 最適化の抑止を意味します。
  マルチスレッドプログラミングにおける諸問
   題の回避を行ってくれるわけではありません。
  メモリマップドI/Oなどを想定した機能であり、
   実の所それ意外はほぼ要無しです。
             C++ Tips Boost.勉強会 #9 つくば   11
文法
const int a = 0; // 前置
int const b = 0; // 後置
const volatile int e = 0; // const+volatile
volatile const int f = 0; // 順番は逆もOKで、意味は同じ。
const int const c = 0; // 重複はNG
const int volatile d = 0; // 重複しない前置+後置はOK
int * const g = 0; // ポインタに対する修飾は必ず後置
int * const * h = 0; // ポインタに対する修飾は必ず後置
const int * const * const i = 0; // ベース+各ポインタに対して


                     C++ Tips Boost.勉強会 #9 つくば   12
文法
const   int a = 0; // a は変更不可
const   int * b = &a; // *b は変更不可, b は変更可
const   int * const c = b; // c,*c は変更不可
const   int * const * const d = &c; // d,*d,**d は変更不可
int e   = 0; // e は変更可
int *   const f = &e; // f は変更不可, *f は変更可
int *   const * const g = &e; // g,*g は変更不可, **g は変更可
int *   const * h = &e; // *h は変更不可, h,**h は変更可



                         C++ Tips Boost.勉強会 #9 つくば      13
文法
// 引数に対するcv修飾に応じて
// 適切にオーバーロードが機能します。
void func(int & a);
void func(int const & a);
void func(int volatile & a);
void func(int const volatile & a);
void func(int && a);




                        C++ Tips Boost.勉強会 #9 つくば   14
文法
// 参照やポインタでない場合は関数シグネチャ的には無視される。
void func(int a);
void func(int const a); // 宣言的には void func(X a); と等価。

// 定義的には違いがある。
void func(int a) {
  a = 0; // OK
}
void func(int const a) {
  a = 0; // constなので書き換えはNG
}
                       C++ Tips Boost.勉強会 #9 つくば   15
文法
class X {
  public:
    void func();
    void func() const;
    void func() volatile;
    void func() const volatile;
    void func() &&;
    // 呼び出し時の this に対するcv修飾に応じて
    // 適切にオーバーロードが機能します。
};

                 C++ Tips Boost.勉強会 #9 つくば   16
文法
class X {
  public:
    X(const int a) const;
    ~X() const;
    // コンストラクタとデストラクタに対するcv修飾はNG
    // ( コンストラクタの引数型に対するcv修飾はOK )

     static void gunc() const;
     // 静的メンバー関数に対するcv修飾はNG
};

                    C++ Tips Boost.勉強会 #9 つくば   17
「よりcv修飾されている」
 cv修飾に関する文脈上「よりcv修飾されて
 いる(more cv-qualified)」という表現が現れ
 ることがありますが、これは規格上次の
 表のように定義されています。



             C++ Tips Boost.勉強会 #9 つくば   18
「よりcv修飾されている」
                 修飾の順序関係

    cv修飾なし           <          const

    cv修飾なし           <          volatile

    cv修飾なし           <          const volatile

        const        <          const volatile

      volatile       <          const volatile
                  C++ Tips Boost.勉強会 #9 つくば      19
const_cast
 cv修飾の状態のみを変換するキャストで
  す。
 名前は const_cast ですが、 volatile につい
  ても修飾を付けたり外したりできます。



               C++ Tips Boost.勉強会 #9 つくば   20
const_cast
 よりcv修飾されている型へは暗黙の型変
 換が行われる為、明示的にキャストする
 必要はありません。
  これは安全なキャストについては暗黙の型変
  換が行われるということであり、const_cast
  が必要になるという事は安全でないキャスト
  を行っているということであり、注意が必要
  です。
             C++ Tips Boost.勉強会 #9 つくば   21
const_cast
int a = 0;
const int * b = &a;
int * c = b;                    // NG
int * d = const_cast<int *>(b); // const を一つ除去
const int * * e = &d;           // NG(後述)
const int * * f = const_cast<const int * *>(&d);
                                // const を一つ付加※
const int * const * g = ...;
int * * h = const_cast<int * *>(g); // const を一つ除去



                        C++ Tips Boost.勉強会 #9 つくば    22
修飾の変換・互換ルール
 cv修飾の差異による型の変換・互換ルー
 ルは「よりcv修飾されている型へは暗黙
 の型変換が行われる」ということさえ頭
 に入れておけば後は概ね直感的に理解し
 やすいものなのですが、厳密にはやや変
 則的で落とし穴が無くもないのでひとつ
 ひとつ見ていきましょう!
         C++ Tips Boost.勉強会 #9 つくば   23
型の互換性:値型
int a = 0;
const int b = a; // コピー
int c = b;       // コピー

...値型の場合、cv修飾の状況の関わらずコピーされる。

※サンプルは代入ですが、関数の引数についてもルールは同じ。




                          C++ Tips Boost.勉強会 #9 つくば   24
型の互換性:参照型
int a   = 0;
const   int & b = a; // よりcv修飾されている型へはOK
int &   c = b;       // 逆方向はNG
int &   d = const_cast<int &>(b); // キャストすればOK




                         C++ Tips Boost.勉強会 #9 つくば   25
型の互換性:ポインター型
int a   = 0;
const   int * b = &a; // よりcv修飾されている型へはOK
int *   c = b;                    // 逆方向はNG
int *   d = const_cast<int *>(b); // キャストすればOK
int *   * e = &b;                 // NG
int *   * f = &d;                 // OK
const   int * * g = &b;           // OK
const   int * * h = &d;           // NG(後述)
int *   const * i = &b;           // NG
int *   const * j = &d;           // OK
const   int * const * k = &b;     // OK
const   int * const * l = &d;     // OK
                         C++ Tips Boost.勉強会 #9 つくば   26
直感的でない型の非互換性
 なぜ const int * * 型の値に int * *
 型の値を代入できないのか?

     int a = 0;
     int * b = &a;
     const int * * c = &b; // NG

               C++ Tips Boost.勉強会 #9 つくば   27
直感的でない型の非互換性
 ポインタ部分を除けば const int 型と
 int 型であり問題はない。

  int a = 0;
  const int b = a; // OK(コピー)

  int c = b; // 逆方向もOK(コピー)
             C++ Tips Boost.勉強会 #9 つくば   28
直感的でない型の非互換性
 ポインタがあっても一段の const int
 * 型と int   * 型であれば同じく問題は
 ない。
    int a   = 0;
    int *   b = &a;
    const   int * c = b; // OK
    int *   d = c;       // NG
                C++ Tips Boost.勉強会 #9 つくば   29
直感的でない型の非互換性
 しかし、 const int * * 型の値に int
 * * 型の値の代入はエラーになる。

     int a = 0;
     int * b = &a;
     const int * * c = &b; // NG

               C++ Tips Boost.勉強会 #9 つくば   30
直感的でない型の非互換性
 もしエラーにならないとしたら?

int a = 0;
int * b = &a;
const int * * c = &b; // 本当はエラー

             C++ Tips Boost.勉強会 #9 つくば   31
直感的でない型の非互換性
 もしエラーにならないとしたら?
int a = 0;
int * b = &a;
const int * * c = &b; // 本当はエラー
const int d = 0;
*c = &d;
*b = 1;

                 C++ Tips Boost.勉強会 #9 つくば   32
直感的でない型の非互換性
 もしエラーにならないとしたら?
int a = 0;
int * b = &a;
const int * * c = &b; // 本当はエラー
const int d = 0;
*c = &d; // *c も &d ともに const int * 型
*b = 1;

                  C++ Tips Boost.勉強会 #9 つくば   33
直感的でない型の非互換性
 もしエラーにならないとしたら?
int a = 0;
int * b = &a;
const int * * c = &b; // 本当はエラー
const int d = 0;
*c = &d; // *c も &d ともに const int * 型
*b = 1; // おかしくね? *b が指してんの d だよ!

               C++ Tips Boost.勉強会 #9 つくば   34
直感的でない型の非互換性
 const int * * 型の値に int * * 型の
 値の代入を許してしまうと結果として
 明示的にキャストを使ってもいないの
 にconst外しを許してしまうことになっ
 てしまう!
  とまぁ、そんなことにならないようにC++のコ
  ンパイラはエラーとして弾いてくれます。
              C++ Tips Boost.勉強会 #9 つくば   35
伝播性
 ある変数をcv修飾するとその変数を参照
 あるいはポインタ経由でアクセスする
 コードが全て同じくcv修飾されているこ
 とが要求されるようになります。



         C++ Tips Boost.勉強会 #9 つくば   36
伝播性
 オブジェクトがcv修飾されると同様な効
 果がそのデータメンバーとメンバー関数
 にも及びます。データメンバーについて
 はさらにそれらのデータメンバーのデー
 タメンバーにも伝播が連鎖します。


         C++ Tips Boost.勉強会 #9 つくば   37
伝播性
 最終的に末端部分で呼び出す外部から提
供されるライブラリの関数(API)が適切に
cv修飾されていない場合、必要に応じて
const_cast を使用してcv修飾を外すことに
なりますが、本当に外してもよいのか注
意する必要があります。

           C++ Tips Boost.勉強会 #9 つくば   38
mutable
 オブジェクトがconst修飾されている場合
 でもデータメンバーにmutableを指定する
 ことでconst修飾を伝播させないこともで
 きます。
  const修飾の伝播のみを無効化するもので、
  mutableが指定されていてもvolatile修飾は伝播
  します。
             C++ Tips Boost.勉強会 #9 つくば   39
mutable
 class X {
   public:
     mutable int a;
     int b;
     X() { }
 };

 const X x;
 x.a = 42; // mutableが効いているので代入可能
 x.b = 42; // constなのでNG

                      C++ Tips Boost.勉強会 #9 つくば   40
伝播性の抜け・・・
class X {                               const X x;
  public:                               //x.a = 42; これはさすがにNG
    int a;                              printf("%drn", x.a); // 0
    int * b;                            *x.b = 1; // これはまだしも・・・
    int & c;                            printf("%drn", x.a); // 1
    X() :a(0), b(&a), c(a) { }          x.c = 2; // しれっと代入可能・・・
};                                      printf("%drn", x.a); // 2




                                 C++ Tips Boost.勉強会 #9 つくば            41
伝播性の抜け・・・
 ほかにもコンストラクタおよびデストラ
  クタにはcv修飾がかからない為、コンス
  トラクタおよびデストラクタ内では本来
  *this がconstな状態であってもcv修飾がか
  かっていないものとして動作します。
 const修飾されているオブジェクトであっ
  ても delete で破棄できちゃったり・・・
            C++ Tips Boost.勉強会 #9 つくば   42
伝播性の抜け・・・
class X {                    const X x;
  public:
    int value;               const X * y = new X();
    X() {                    delete y;
        value = 0;
        func();
    }
    ~X() {
        value = 42;
        func();
    }
    void func();
};
                      C++ Tips Boost.勉強会 #9 つくば       43
一時オブジェクトの延命
 const且つ参照で受け取ることで一時オブ
  ジェクトをその受け取った側の変数と同
  じスコープで延命できます。
std::string func();

std::string name1 = func();              //   コピー
const std::string name2 = func();        //   コピー
const std::string & name3 = func();      //   一時オブジェクトの延命
std::string & name4 = func();            //   NG
                          C++ Tips Boost.勉強会 #9 つくば     44
volatileメンバー関数でロック
 ちょっとトリッキーなvolatile修飾の使い
 方として、スレッド間で共有して使われ
 ることがあるオブジェクトをvolatile修飾
 によってマークし、そのメンバー関数が
 呼び出される際にvolatile修飾されている
 場合にのみロックを行う手法があります。

           C++ Tips Boost.勉強会 #9 つくば   45
volatileメンバー関数でロック
class X {
  public:
    int func() { ... }
    int func() volatile {
        return auto_lock(), const_cast<X *>(this)->func();
    }
};

X a;
a.func(); // スレッド間共有しないインスタンスではロックせずに処理
volatile X b;
b.func(); //スレッド間共有するインスタンスではロックして処理

                              C++ Tips Boost.勉強会 #9 つくば      46
C++ Tips

質疑応答
C++ Tips

ご清聴ありがとうございました。

C++ tips4 cv修飾編