名前なし/匿名の名前空間と静的関数


508

C ++の機能は、次のように名前のない(匿名の)名前空間を作成する機能です。

namespace {
    int cannotAccessOutsideThisFile() { ... }
} // namespace

そのような機能は役に立たないと思うでしょう-名前空間の名前を指定できないので、外部からその中の何かにアクセスすることは不可能です。ただし、これらの名前のない名前空間、暗黙的なusing句があるかのように、それらが作成されたファイル内でアクセスできます。

私の質問は、静的関数を使用するよりも、なぜまたはいつこれが望ましいのでしょうか?または、それらは本質的にまったく同じことを行う2つの方法ですか?


13
C ++ 11ではstatic、このコンテキストでの使用は非推奨ではありませんでした。ただし無名の名前空間には、より優れた代替手段ですstatic、そこにいるとき、それが失敗したインスタンスstatic救助に来ます
legends2k 2013

回答:


332

C ++標準では、セクション7.3.1.1の名前なし名前空間の段落2を読みます。

名前空間スコープでオブジェクトを宣言する場合、staticキーワードの使用は非推奨です。unnamed-namespaceは、より優れた代替手段を提供します。

staticは、オブジェクト、関数、および匿名共用体の名前にのみ適用され、型宣言には適用されません。

編集:

このstaticキーワードの使用を廃止する決定(翻訳単位での変数宣言の可視性に影響を与える)は逆になりました(ref)。この場合、静的または名前のない名前空間を使用すると、まったく同じことを行う2つの方法に戻ります。詳細については、この SOの質問を参照しください。

名前空間のない名前空間には、翻訳単位ローカルタイプを定義できるという利点があります。詳細については、この SOの質問を参照してください。

これは私の注意を喚起してくれたMike Percyの功績です。


39
Head Geekは、関数に対してのみ使用される静的キーワードについて尋ねます。名前空間スコープで宣言されたエンティティに適用される静的キーワードは、その内部リンケージを指定します。匿名の名前空間で宣言されたエンティティには外部リンケージ(C ++ / 3.5)がありますが、一意に名前が付けられたスコープに存在することが保証されています。名前のない名前空間のこの匿名性は、宣言を効果的に隠し、翻訳単位内からのみアクセスできるようにします。後者は、staticキーワードと同じように効果的に機能します。
mloskot 2009

5
外部リンケージの欠点は何ですか?これはインライン化に影響しますか?
Alex

17
C ++設計委員会のメンバーで、静的キーワードが廃止予定であると言った人は、おそらく大規模な現実世界のシステムで巨大なCコードを使用することは決してないでしょう。ブロック)
Calmarius

23
この回答は「c ++匿名名前空間」の上位結果としてGoogleに表示されるため、staticの使用は廃止されなくなったことに注意してください。詳細については、stackoverflow.com / questions / 4726570 / およびopen-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012を参照してください。
マイケルパーシー

2
@ErikAronestyそれは間違っているようです。再現可能な例はありますか?C ++ 11以降、および一部のコンパイラではその前でも、名前namespaceのないは暗黙的に内部リンケージを持っているため、違いはありません。以前は不適切な表現から生じていた可能性がある問題は、C ++ 11でこれを要件にすることで解決されました。
underscore_d

73

メソッドを匿名の名前空間に配置すると、誤ってOne Definition Ruleに違反することがなくなり、リンクする他のメソッドと同じようにヘルパーメソッドに名前を付けることを心配する必要がなくなります。

そして、lukeが指摘しているように、匿名の名前空間は、静的メンバーよりも標準で好まれています。


2
静的メンバー関数ではなく、静的スタンドアロン関数(つまり、ファイルスコープ関数)を参照していました。静的なスタンドアロン関数は、名前のない名前空間の関数とほとんど同じなので、問題になります。
オタクの頭

2
ああ。まあ、ODRはまだ適用されます。段落を削除するように編集されました。
2008

私が取得したように、静的関数のODRは、ヘッダーで定義されていて、このヘッダーが複数の変換単位に含まれていると機能しません。この場合、同じ関数の複数のコピーを受け取ります
Andriy Tylychko '27 / 07/27

@Andy T:ヘッダーが含まれている場合、「複数の定義」は実際には表示されません。プリプロセッサが処理します。プリプロセッサが生成した出力を研究する必要がない限り、それは私にはかなりエキゾチックで珍しいように見えます。また、「#ifndef SOME_GUARD-#define SOME_GUARD ...」のように、ヘッダーファイルに「ガード」を含めることをお勧めします。これにより、プリプロセッサーが同じヘッダーを2回含めるのを防ぐことができます。
Nikita Vorontsov 2014年

@NikitaVorontsovガードは、同じ変換ユニットに同じヘッダーを含めることを防ぐことができますが、異なる変換ユニットで複数の定義を許可します。これにより、「複数の定義」リンカーエラーが発生する可能性があります。
アレックス、

37

staticが驚くべき効果をもたらすエッジケースが1つあります(少なくとも私にとっては)。C ++ 03標準は14.6.4.2/1で述べています:

テンプレートパラメータに依存する関数呼び出しで、関数名がunqualified-idであるtemplate-idではない場合、次の場合を除いて、通常の検索ルール(3.4.1、3.4.2)を使用して候補関数が検索されます。

  • 非修飾名ルックアップ(3.4.1)を使用したルックアップの部分では、テンプレート定義コンテキストからの外部リンケージを持つ関数宣言のみが見つかります。
  • 関連する名前空間を使用したルックアップの一部(3.4.2)では、テンプレート定義コンテキストまたはテンプレートインスタンス化コンテキストのいずれかで見つかった外部リンケージを持つ関数宣言のみが見つかります。

...

以下のコードは、あなたが期待するものではfoo(void*)なくfoo(S const &)、呼び出すでしょう。

template <typename T>
int b1 (T const & t)
{
  foo(t);
}

namespace NS
{
  namespace
  {
    struct S
    {
    public:
      operator void * () const;
    };

    void foo (void*);
    static void foo (S const &);   // Not considered 14.6.4.2(b1)
  }

}

void b2()
{
  NS::S s;
  b1 (s);
}

それ自体はそれほど大きな問題ではないかもしれませんが、完全に準拠したC ++コンパイラ(つまり、をサポートしているコンパイラexport)の場合、staticキーワードには他の方法では利用できない機能がまだあることを強調しています。

// bar.h
export template <typename T>
int b1 (T const & t);

// bar.cc
#include "bar.h"
template <typename T>
int b1 (T const & t)
{
  foo(t);
}

// foo.cc
#include "bar.h"
namespace NS
{
  namespace
  {
    struct S
    {
    };

    void foo (S const & s);  // Will be found by different TU 'bar.cc'
  }
}

void b2()
{
  NS::S s;
  b1 (s);
}

名前のない名前空間の関数がADLを使用するテンプレートで見つからないことを保証する唯一の方法は、それを作成することstaticです。

最新のC ++の更新

C ++ '11以降、名前のない名前空間のメンバーには、暗黙的に内部リンケージがあります(3.5 / 4):

名前のない名前空間、または名前のない名前空間内で直接的または間接的に宣言された名前空間には、内部リンクがあります。

しかし、同時に14.6.4.2/1が更新され、リンケージの言及が削除されました(これはC ++ '14から取得されました)。

postfix-expressionが従属名である関数呼び出しの場合、次の場合を除いて、通常の検索ルール(3.4.1、3.4.2)を使用して候補関数が見つかります。

  • 非修飾名ルックアップ(3.4.1)を使用したルックアップの部分では、テンプレート定義コンテキストからの関数宣言のみが見つかります。

  • 関連する名前空間を使用したルックアップの一部(3.4.2)では、テンプレート定義コンテキストまたはテンプレートインスタンス化コンテキストのいずれかで見つかった関数宣言のみが見つかります。

その結果、名前空間の静的メンバーと名前のないメンバーとの間のこの特定の違いはなくなりました。


3
exportキーワードは冷たい死んでいるはずではありませんか?"export"をサポートする唯一のコンパイラは実験的なものであり、予期しない副作用(予期されていなかったことに加えて)のため、予期しない副作用のために "export"は他の実装もされません
paercebal


3
Edison Design Group(EDG)のフロントエンドは、実験的なものではありません。ほぼ間違いなく、世界で最も標準に準拠したC ++実装です。インテルC ++コンパイラーはEDGを使用します。
Richard Corden、

1
「予期しない副作用」がないのはどのC ++機能ですか?エクスポートの場合、名前のない名前空間関数は別のTUから検出されます。これは、テンプレート定義を直接含めた場合と同じです。こんな感じじゃなかったらもっとびっくり!
Richard Corden、

私はあなたがそこにタイプミスを持っていると思います-動作するためNS::Sに、S中にいる必要はありませんnamespace {}か?
エリック

12

最近、コードで静的キーワードを匿名の名前空間に置き換え始めましたが、デバッガで名前空間の変数を検査できないという問題がすぐに発生しました。私はVC60を使用していたため、それが他のデバッガーで問題でないかどうかはわかりません。私の回避策は、「モジュール」名前空間を定義することでした。そこで、cppファイルの名前を付けました。

たとえば、私のXmlUtil.cppファイルではXmlUtil_I { ... }、すべてのモジュール変数と関数の名前空間を定義しています。そうXmlUtil_I::すれば、デバッガーで修飾を適用して変数にアクセスできます。この場合、は、他の場所で使用したい_Iパブリックネームスペースと区別しXmlUtilます。

本当に匿名のアプローチと比較した場合のこのアプローチの潜在的な欠点は、他のモジュールで名前空間修飾子を使用することにより、誰かが目的の静的スコープに違反する可能性があることです。それが大きな懸念であるかどうかはわかりません。


7
私もこれを行いましたが#if DEBUG namespace BlahBlah_private { #else namespace { #endif、を使用しているため、「モジュール名前空間」はデバッグビルドにのみ存在し、それ以外の場合は真の匿名名前空間が使用されます。デバッガーがこれを処理するための素晴らしい方法を提供してくれるといいですね。Doxygenも混乱しています。
クリストファージョンソン

4
名前のない名前空間は、実際にはstaticの実行可能な置き換えではありません。静的とは、「実際には、これがTUの外部にリンクされることは決してない」ことを意味します。名前のない名前空間は、「TUの外部にある親クラスから呼び出された場合に備えて、ランダムな名前としてエクスポートされる」という意味です...
Erik Aronesty

7

そのための静的キーワードの使用は、C ++ 98標準では非推奨です。staticの問題は、型定義に適用されないことです。これは、さまざまなコンテキストでさまざまな方法で使用されるオーバーロードされたキーワードでもあるため、名前のない名前空間は少し単純化されます。


1
単一の翻訳単位でのみタイプを使用する場合は、.cppファイル内で宣言します。とにかく、他の翻訳ユニットからはアクセスできません。
Calmarius

4
あなたは思いませんか?しかし、同じアプリケーションの別の翻訳単位(= cpp-file)が同じ名前の型を宣言した場合、デバッグが困難な問題が発生します:-)。たとえば、一方のタイプのvtableがもう一方のメソッドを呼び出すときに使用される状況になる可能性があります。
avl_sweden

1
もう非推奨ではありません。また、型の定義はエクスポートされないため、意味がありません。staticsは、スタンドアロン関数とグローバル変数に役立ちます。名前のない名前空間はクラスに役立ちます。
Erik Aronesty、2015年

6

経験から、以前は静的な関数を匿名の名前空間に入れるのはC ++の方法ですが、古いコンパイラではこれに問題が発生することがあります。私は現在、ターゲットプラットフォーム用にいくつかのコンパイラを使用していますが、より最近のLinuxコンパイラは、匿名の名前空間に関数を配置することで問題ありません。

しかし、Solarisで実行されている古いコンパイラは、不特定の将来のリリースまで使用できる場合があり、それを受け入れることもあれば、エラーとしてフラグを立てることもあります。エラーは私を心配するものではなく、それがそれを受け入れるときにそれが何をしているのかもしれません。ですから、全体的にモダンになるまでは、静的な(通常はクラススコープの)関数を使用しており、ここでは匿名の名前空間を使用します。


3

さらに、この例のように変数で静的キーワードを使用する場合:

namespace {
   static int flag;
}

マッピングファイルには表示されません。


7
そうすれば、匿名の名前空間はまったく必要ありません。
Calmarius

2

匿名の名前空間と静的関数のコンパイラ固有の違いは、次のコードのコンパイルで確認できます。

#include <iostream>

namespace
{
    void unreferenced()
    {
        std::cout << "Unreferenced";
    }

    void referenced()
    {
        std::cout << "Referenced";
    }
}

static void static_unreferenced()
{
    std::cout << "Unreferenced";
}

static void static_referenced()
{
    std::cout << "Referenced";
}

int main()
{
    referenced();
    static_referenced();
    return 0;
}

このコードをVS 2017(レベル4警告フラグ/ W4を指定して警告C4505を有効にするように指定:参照されていないローカル関数が削除されました)と-Wunused-functionまたは-Wallフラグを指定してgcc 4.9をコンパイルすると、VS 2017は未使用の静的関数。gcc 4.9以降およびclang 3.3以降では、名前空間内の参照されていない関数に対する警告と、未使用の静的関数に対する警告が生成されます。

gcc 4.9およびMSVC 2017のライブデモ


2

個人的には、次の理由により、名前のない名前空間よりも静的関数を好みます。

  • 関数の定義だけから、それがコンパイルされる翻訳単位に対してプライベートであることは明白で明確です。名前のない名前空間では、関数が名前空間にあるかどうかを確認するためにスクロールして検索する必要がある場合があります。

  • 名前空間内の関数は、一部の(古い)コンパイラーによってexternとして扱われる場合があります。VS2017では、それらはまだexternです。このため、関数が名前のない名前空間にある場合でも、静的にマークすることができます。

  • 静的関数の動作はCまたはC ++と非常に似ていますが、名前のない名前空間は明らかにC ++のみです。名前のない名前空間はインデントに追加のレベルを追加し、私はそれが好きではありません:)

したがって、関数でのstaticの使用が廃止されなくなったことを嬉しく思います


匿名の名前空間の関数は、外部リンケージを持っていることになっています。彼らはただ彼らをユニークにするために壊されています。staticキーワードのみが実際に関数にローカルリンケージを適用します。また、確かに狂った狂人だけが実際に名前空間のインデントを追加しますか?
Roflcopter4 2018

0

あなたの質問を読んでいるときだけこの機能を知ったので、私は推測することしかできません。これには、ファイルレベルの静的変数に比べていくつかの利点があります。

  • 匿名の名前空間は相互にネストでき、シンボルがエスケープできない複数レベルの保護を提供します。
  • 複数の匿名名前空間を同じソースファイルに配置して、同じファイル内に実質的に異なる静的レベルのスコープを作成できます。

誰かが実際のコードで匿名の名前空間を使用したかどうかを知りたいと思います。


4
良い憶測ですが、間違っています。これらの名前空間のスコープはファイル全体です。
Konrad Rudolph

正確には当てはまりません。別のネームスペース内で匿名ネームスペースを定義した場合、それはまだファイル全体のみであり、そのネームスペース内にあるものとしてのみ表示できます。それを試してみてください。
グレッグロジャース

私は間違っている可能性がありますが、それはファイル全体ではありません。匿名の名前空間ののコードのみアクセスできます。これは微妙なことであり、通常、複数の匿名名前空間でソースを汚染したくありません...それでも、これには用途があります。
paercebal 2008年

0

違いは、マングルされた識別子の名前です(_ZN12_GLOBAL__N_11bEvs _ZL1b、これは重要ではありませんが、どちらもシンボルテーブルのローカルシンボルにアセンブルされます(.globalasmディレクティブがない場合))。

#include<iostream>
namespace {
   int a = 3;
}

static int b = 4;
int c = 5;

int main (){
    std::cout << a << b << c;
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_11aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4
_ZL1b:
        .long   4
        .globl  c
        .align 4
        .type   c, @object
        .size   c, 4
c:
        .long   5
        .text

ネストされた匿名の名前空間について:

namespace {
   namespace {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_112_GLOBAL__N_11aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

翻訳単位内のすべての第1レベルの匿名名前空間は互いに結合され、翻訳単位内のすべての第2レベルのネストされた匿名名前空間は互いに結合されます

匿名の名前空間にネストされた(インライン)名前空間を持つこともできます

namespace {
   namespace A {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11A1aE, @object
        .size   _ZN12_GLOBAL__N_11A1aE, 4
_ZN12_GLOBAL__N_11A1aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

which for the record demangles as:
        .data
        .align 4
        .type   (anonymous namespace)::A::a, @object
        .size   (anonymous namespace)::A::a, 4
(anonymous namespace)::A::a:
        .long   3
        .align 4
        .type   b, @object
        .size   b, 4

匿名のインライン名前空間を持つこともできますが、私が知る限りinline、匿名の名前空間では効果がありません

inline namespace {
   inline namespace {
       int a = 3;
    }
}

_ZL1b_Zこれはマングルされた識別子であることを意味します。Lは、を介したローカルシンボルであることを意味しstaticます。1識別子の長さb、そして識別子b

_ZN12_GLOBAL__N_11aE _Zこれはマングルされた識別子であることを意味します。Nこれは、名前空間12が匿名名前空間名の長さ_GLOBAL__N_1、次に匿名名前空間名_GLOBAL__N_1、次に1識別子の長さ、が識別子aaあり、名前空間にある識別子aE閉じることを意味します。

_ZN12_GLOBAL__N_11A1aE その中に別の名前空間レベルがあることを除いて、上記と同じです 1A

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.