名前空間
変種
操作

スコープ

提供: cppreference.com
< cpp‎ | language
 
 
C++言語
一般的なトピック
フロー制御
条件付き実行文
繰り返し文 (ループ)
ジャンプ文
関数
関数宣言
ラムダ関数宣言
inline 指定子
例外指定 (C++20未満)
noexcept 指定子 (C++11)
例外
名前空間
指定子
decltype (C++11)
auto (C++11)
alignas (C++11)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点
文字 - 文字列 - nullptr (C++11)
ユーザ定義 (C++11)
ユーティリティ
属性 (C++11)
typedef 宣言
型エイリアス宣言 (C++11)
キャスト
暗黙の変換 - 明示的な変換
static_cast - dynamic_cast
const_cast - reinterpret_cast
メモリ確保
クラス
クラス固有の関数特性
特別なメンバ関数
テンプレート
その他
 
 

C++ のプログラムに現れる名前はそれぞれ、そのスコープと呼ばれる、ソースコードの一部分 (不連続なこともあります) 内でのみ有効です。

スコープ内では、名前をその宣言と紐付けるために、非修飾名の名前探索が使用できます。

目次

[編集] ブロックスコープ

ブロック (複文) 内の宣言によって導入された変数の潜在的なスコープは、宣言の地点で始まり、ブロックの終わりで終わります。 実際のスコープは、同一の名前を導入する宣言を持つネストしたスコープがない限り、潜在的なスコープと同じです (ある場合は、ネストした宣言の潜在的なスコープ全体が外側の宣言のスコープから除外されます)。

int main()
{
    int a = 0; // 1つめの「a」のスコープが始まります。
    ++a; // 名前「a」はスコープ内であり、1つめの「a」を参照します。
    {
        int a = 1; // 2つめの「a」のスコープが始まります。
                   // 1つめの「a」のスコープは中断されます。
        a = 42;    // 「a」はスコープ内であり、2つめの「a」を参照します。
    } // ブロックの終わり。 2つめの「a」のスコープが終わります。
      //                    1つめの「a」のスコープが再開されます。
} // ブロックの終わり。 1つめの「a」のスコープが終わります。
int b = a; // エラー、名前「a」はスコープ内ではありません。

例外ハンドラ内で宣言された名前の潜在的なスコープは、宣言の地点で始まり、例外ハンドラが終わったときに終わり、別の例外ハンドラ内または囲っているブロック内ではスコープ内ではありません。

try {   
    f();
} catch(const std::runtime_error& re) { // re のスコープが始まります。
    int n = 1; // n のスコープが始まります。
    std::cout << re.what(); // re はスコープ内です。
} // re のスコープが終わり、 n のスコープが終わります。
 catch(std::exception& e) {
    std::cout << re.what(); // エラー、 re はスコープ内ではありません。
    ++n; // エラー、 n はスコープ内ではありません。
}

for ループinit-statementfor ループcondition範囲 for ループrange_declarationif 文または switch 文init-statement (C++17以上)if 文または while ループまたは switch 文condition 内で宣言された名前の潜在的なスコープは、宣言の地点で始まり、その制御文の終わりで終わります。

Base* bp = new Derived;
if(Derived* dp = dynamic_cast<Derived*>(bp))
{
    dp->f(); // dp はスコープ内です。
} // dp のスコープが終わります。
 
for(int n = 0; // n のスコープが始まります。
    n < 10;    // n はスコープ内です。
    ++n)       // n はスコープ内です。
{
    std::cout << n << ' '; // n はスコープ内です。
} // n のスコープが終わります。

[編集] 関数の引数のスコープ

関数の引数 (ラムダ式の引数を含みます) または関数ローカルな定義済み変数の潜在的なスコープは、宣言の地点で始まります。

  • 最も近い囲っている関数宣言子が関数定義の宣言子でない場合、その潜在的なスコープはその関数宣言子の終わりで終わります。
  • そうでなければ、その潜在的なスコープは関数 try ブロックの最後の例外ハンドラの終わり、または関数 try ブロックを使用していない場合は関数本体の終わりで終わります。
const int n = 3;
 
int f1(int n,     // グローバル変数「n」のスコープは中断されます。
                  // 引数「n」のスコープが始まります。
       int y = n); // エラー、デフォルト引数が引数を参照しています。
 
int (*(*f2)(int n))[n]; // OK、関数の引数「n」のスコープは
                        // 配列宣言子内の関数宣言子の終わりで終わり、
                        // グローバル変数「n」がスコープ内になります。
// (これは int 3個の配列を指すポインタを返す関数を指すポインタを宣言します)
 
// 比べてみてください。
auto (*f3)(int n)->int (*)[n]; // エラー、配列の境界としての引数「n」
 
 
int f(int n = 2)  // 「n」のスコープが始まります。
try // 関数 try ブロック
{         // 関数の本体が始まります。
   ++n;   // 「n」はスコープ内であり、関数の引数を参照します。
   {
      int n = 2; // ローカル変数「n」のスコープが始まります。
                 // 関数の引数「n」のスコープは中断されます。
      ++n; // 「n」はこのブロック内のローカル変数を参照します。
    }            // ローカル変数「n」のスコープが終わります。
                 // 関数の引数「n」のスコープが再開されます。
} catch(...) {
   ++n; // 「n」はスコープ内であり、関数の引数を参照します。
   throw;
} // 最後の例外ハンドラの終わり。 関数の引数「n」のスコープが終わります。
int a = n; // OK、グローバル変数「n」がスコープ内です。

[編集] 関数スコープ

関数の内側で宣言されたラベル (ラベルだけです) は、その関数内のすべての場所 (すべてのネストしたブロック内、それ自身の宣言の前でも後でも) でスコープ内です。

void f()
{
   {   
       goto label; // label は、たとえ後で宣言されていても、スコープ内です。
label:;
   }
   goto label; // ラベルはブロックスコープを無視します。
}
 
void g()
{
    goto label; // エラー、 label は g() 内ではスコープ内ではありません。
}

[編集] 名前空間スコープ

名前空間内で宣言された任意のエンティティの潜在的なスコープは、 宣言で始まり、その後の同じ名前空間名に対するすべての名前空間の定義、および、その名前またはその名前空間全体を別のスコープに導入したあらゆる using 指令について、そのスコープの残りの部分を、連結したものから構成されます。

翻訳単位のトップレベルスコープ (「ファイルスコープ」または「グローバルスコープ」) も名前空間であり、正式には「グローバル名前空間スコープ」と言います。 グローバル名前空間スコープで宣言されたあらゆるエンティティの潜在的なスコープは、その宣言で始まり、翻訳単位の終わりまで続きます。

無名名前空間内またはインライン名前空間内で宣言されたエンティティのスコープは、囲っている名前空間を含みます。

namespace N { // (グローバル名前空間のメンバとしての) N のスコープが始まります。
    int i; // i のスコープが始まります。
    int g(int a) { return a; } // g のスコープが始まります。
    int j(); // j のスコープが始まります。
    void q(); // q のスコープが始まります。
    namespace {
        int x; // x のスコープが始まります。
    } // x のスコープは終わりません。
    inline namespace inl { // inl のスコープが始まります。
      int y; // y のスコープが始まります。
    } // y のスコープは終わりません。
} // i,g,j,q,inl,x,y のスコープが中断されます。
 
namespace {
    int l=1; // l のスコープが始まります。
} // l のスコープは終わりません (l は無名名前空間のメンバです)。
 
namespace N { // i,g,j,q,inl,x,y のスコープが再開されます。
    int g(char a) {  // N::g(int) をオーバーロードします。
        return l+a;  // 無名名前空間から来た l がスコープ内です。
    }
    int i; // エラー、定義の重複 (i はすでにスコープ内です)
    int j(); // OK、関数宣言の繰り返しは許されます。
    int j() { // OK、以前に宣言された N::j() の定義。
        return g(i); // N::g(int) を呼びます。
    }
    int q(); // エラー、 q は異なる戻り値型ですでにスコープ内です。
} // i,g,j,q,inl,x,y のスコープが中断されます。
 
int main() {
    using namespace N; // i,g,j,q,inl,x,y のスコープが再開されます。
    i = 1; // N::i はスコープ内です。
    x = 1; // N::(無名)::x はスコープ内です。
    y = 1; // N::inl::y はスコープ内です。
    inl::y = 2; // N::inl もスコープ内です。
} // i,g,j,q,inl,x,y のスコープが中断されます。

[編集] クラススコープ

クラス内で宣言された名前の潜在的なスコープは、宣言の地点で始まり、クラス本体の残りおよびすべての関数本体 (たとえクラス定義の外側やその名前の宣言より前で定義されていても)、デフォルト引数、例外指定、クラス内波括弧または等号初期化子契約条件 (C++20以上)、および再帰的にネストしたクラス内のそれらのものすべてを含みます。

class X {
    int f(int a = n) { // X::n はデフォルト引数内でスコープ内です。
         return a*n;   // X::n は関数本体内でスコープ内です。
    }
    using r = int;
    r g();
    int i = n*2;   // X::n は初期化子内でスコープ内です。
 
//  int x[n];      // エラー、 n はクラス本体内ではスコープ内ではありません。
    static const int n = 1;
    int x[n];      // OK、 n は今やクラス本体内でスコープ内です。
};
 
//r X::g() {       // エラー、 r はクラス外側のメンバ関数本体の外側ではスコープ内ではありません。
auto X::g()->r {   // OK、後置戻り値型 X::r はスコープ内です。
    return n;      // X::n はクラス外側のメンバ関数本体の内側ではスコープ内です。
}

名前が宣言される前にクラス本体内で使用され、その名前に対する別の宣言がスコープ内である場合、プログラムは ill-formed です (診断は要求されません)

typedef int c; // ::c
enum { i = 1 }; // ::i
class X {
    char v[i]; // エラー、この時点では i は ::i を参照しますが、
               // X::i も存在しています。
    int f() {
         return sizeof(c); // OK メンバ関数の内側では ::c ではなく X::c がスコープ内です。
    }
    char c; // X::c
    enum { i = 2 }; // X::i
};
 
typedef char* T;
struct Y {
    T a; // エラー、この時点では T は ::T を参照しますが、
         // Y::T も存在しています。
    typedef long T;
    T b;
};

あらゆるクラスメンバの名前は、以下の4つの文脈でのみ使用できます。

  • それ自身のクラススコープ内で、または派生クラスのクラススコープ内
  • そのクラスまたはその派生クラスの型の式に適用された . 演算子の後
  • そのクラスまたはその派生クラスへのポインタ型の式に適用された -> 演算子の後
  • そのクラスの名前またはその派生クラスの名前に適用された :: 演算子の後

[編集] 列挙スコープ

スコープ付き列挙で導入された列挙子の名前は、宣言の地点で始まり、その enum 指定子の終わりで終わります (それに対して、スコープなし列挙子は enum 指定子の終わりの後もスコープ内です)。

enum e1_t { // スコープなし列挙
  A,
  B = A*2
}; // A と B のスコープは終わりません。
 
enum class e2_t { // スコープ付き列挙
    SA,
    SB = SA*2 // SA はスコープ内です。
}; // SA と SB のスコープが終わります。
 
e1_t e1 = B; // OK、 B はスコープ内です。
// e2_t e2 = SB; // エラー、 SB はスコープ内ではありません。
e2_t e2 = e2_t::SB; // OK。

[編集] テンプレート引数スコープ

テンプレート引数名の潜在的なスコープは、宣言の地点で即時に始まり、それが導入された最も小さなテンプレート宣言の終わりまで続きます。 特に、テンプレート引数は後続のテンプレート引数の宣言および基底クラスの指定で使用できますが、先行するテンプレート引数の宣言では使用できません。

template< typename T, // T のスコープが始まります。
          T* p,       // 非型引数のために T を使用できます。
          class U = T // デフォルト型のために T を使用できます。
        >
class X : public Array<T> // 基底クラスの名前で T を使用できます。
{
   // 本体の中でも同様に T を使用できます。
}; // T と U のスコープが終わります。 X のスコープは続きます。

テンプレートテンプレート引数の引数の名前の潜在的なスコープは、その名前が現れた最も小さなテンプレート引数リストです。

template< template< // テンプレートテンプレート引数
                    typename Y,     // Y のスコープが始まります。
                    typename G = Y // Y はスコープ内です。
                  > // Y と G のスコープが終わります。
          class T,
//          typename U = Y // エラー、 Y はスコープ内ではありません。
          typename U
        >
class X
{
}; // T と U のスコープが終わります。

他のネストしたスコープと同様に、テンプレート引数の名前は、それ自身の期間の間、外側のスコープから来た同じ名前を隠蔽します。

typedef int N;
template< N X, // int 型の非型引数
          typename N, // この N のスコープが始まります。 ::N のスコープは中断されます。
          template<N Y> class T // ここでは N は int ではなくテンプレート引数です。
         > struct A;

[編集] 宣言の地点

スコープは宣言の地点で始まります。 これは以下のように位置決めされます。

変数およびその他の単純宣言によって導入された名前の場合、宣言の地点はその名前の宣言子の直後かつその初期化子 (もしあれば) の前です。

unsigned char x = 32; // 1つめの「x」のスコープが始まります。
{
    unsigned char x = x; // 2つめの「x」のスコープは初期化子 (= x) の前で始まります。
                         // これは2つめの「x」を値32で初期化しません。
                         // これは2つめの「x」を自分自身の (不定な) 値で初期化します。
}
 
std::function<int(int)> f = [&](int n){return n>1 ? n*f(n-1) : n;};
           // 関数「f」の名前はラムダの中でスコープ内であり、
           // 正しく参照でキャプチャでき、再帰関数を与えます。
const int x = 2; // 1つめの「x」のスコープが始まります。
{
    int x[x] = {}; // 2つめの x のスコープは初期化子 (= {}) の前かつ宣言子 (x[x]) の後で
                   // 始まります。 宣言子内では外側の「x」が未だスコープ内です。
                   // この宣言は int 2個の配列を宣言します。
}

構造化束縛の宣言の地点は、その構造化束縛宣言の identifier-list の直後ですが、構造化束縛の初期化子は導入中のいかなる名前も参照することが禁止されています。

(C++17以上)

クラスまたはテンプレートの宣言の地点は、 class-head 内のそのクラスを表す識別子 (またはそのテンプレート特殊化を表す template-id) の直後であり、基底クラスのリストではすでにスコープ内です。

// 名前「S」はそれが現れた直後からスコープ内であるため、
// 基底クラスのリストで使用することができます。
struct S: std::enable_shared_from_this<S> 
{
};

列挙の宣言の地点は、 enum 指定子または不透明な enum 宣言いずれか最初に使用された方にそれを表す識別子が現れた直後です。

enum E : int { // E はすでにスコープ内です。
    A = sizeof(E)
};

型エイリアスまたはエイリアステンプレートの宣言の地点は、そのエイリアスの参照先の type-id の直後です。

using T = int; // T の宣言の地点はセミコロンです。
using T = T;   // T = int と同じです。

列挙子の宣言の地点は、その定義の直後です (変数の場合と異なり初期化子の前ではありません)。

const int x = 12;
{
    enum { x = x + 1, // 宣言の地点はコンマであり、 x は 13 に初期化されます。
           y = x + 1  // 列挙子 x は今やスコープ内であり、 y は 14 に初期化されます。
         };
}

注入されたクラス名の宣言の地点は、そのクラス (またはクラステンプレート) 定義の開き波括弧の直後です。

template<typename T>
struct Array
// : std::enable_shared_from_this<Array> // エラー、注入されたクラス名はスコープ内ではありません。
   : std::enable_shared_from_this< Array<T> > // OK、テンプレート名 Array はスコープ内です。
{ // 注入されたクラス名 Array はここからパブリックメンバ名であるかのようにスコープ内になります。
    Array* p; // Array<T> へのポインタ。
};

[編集] 参考文献

  • C++11 standard (ISO/IEC 14882:2011):
  • 3.3 Scope [basic.scope]
  • C++98 standard (ISO/IEC 14882:1998):
  • 3.3 Declarative regions and scopes [basic.scope]

[編集] 関連項目

スコープC言語リファレンス