メインコンテンツ

不明瞭な宣言の構文

宣言の構文はオブジェクト宣言としてまたは関数宣言の一部として解釈可能

説明

この欠陥は、オブジェクト宣言と関数/パラメーター宣言のどちらが意図されたものかを宣言から判断できない場合に発生します。このあいまいさは、多くの場合、最も厄介な解析と呼ばれます。

たとえば、以下の宣言はあいまいです。

  • ResourceType aResource();

    aResourceResourceType 型の変数を返す関数なのか、ResourceType 型のオブジェクトなのか、すぐには理解できません。

  • TimeKeeper aTimeKeeper(Timer());

    aTimeKeeperTimer 型の名前なしオブジェクトで構築されるオブジェクトなのか、パラメーターとして名前なし関数ポインター型をもつ関数なのか、すぐには理解できません。この関数ポインターは、引数がなく戻り値の型が Timer の関数を参照しています。

チェッカーは、グローバル スコープによるあいまいな宣言にフラグを設定します。たとえば、解析では、Type a() の形式を使用したグローバル スコープによる宣言にはフラグが設定されません。ここで、Type は既定のコンストラクターを使用したクラス型です。解析では、aType 型を返す関数として解釈されます。

リスク

宣言があいまいな場合、C++ 規格は構文の特定の解釈を選択します。次に例を示します。

  • ResourceType aResource();
    これは関数 aResource の宣言として解釈されます。

  • TimeKeeper aTimeKeeper(Timer());
    これは、関数ポインター型の名前なしパラメーターをもつ関数 aTimeKeeper の宣言として解釈されます。

開発者またはコード レビュー担当者が別の解釈を想定している場合、予期しない結果になる可能性があります。

たとえば、理解するのが困難なコンパイル エラーが後で発生する場合があります。既定の解釈は関数宣言を示すので、この関数をオブジェクトとして使用すると、コンパイラはコンパイル エラーを報告する可能性があります。このコンパイル エラーは、適切なコンストラクターを使用せずに関数からオブジェクトへの変換が試みられていることを示します。

修正方法

宣言を明確にします。たとえば、これらのあいまな宣言は以下のように修正します。

  • ResourceType aResource();

    オブジェクト宣言:

    宣言が既定のコンストラクターを使用して初期化されたオブジェクトのことを指している場合は、次のように書き換えます。

    ResourceType aResource;
    これは C++11 より前の場合です。または、次のようにします。
    ResourceType aResource{};
    これは、C++11 以降の場合です。

    関数宣言:

    宣言が関数のことを指している場合、関数の typedef を使用します。

    typedef ResourceType(*resourceFunctionType)();
    resourceFunctionType aResource;

  • TimeKeeper aTimeKeeper(Timer());

    オブジェクト宣言:

    宣言がクラス Timer の名前なしオブジェクトで初期化されたオブジェクト aTimeKeeper のことを指している場合、かっこのペアを追加します。

    TimeKeeper aTimeKeeper( (Timer()) );
    これは C++11 より前の場合です。または、中かっこを使用します。
    TimeKeeper aTimeKeeper{Timer{}};
    これは、C++11 以降の場合です。

    関数宣言:

    宣言が関数ポインター型の名前なしパラメーターをもつ関数 aTimeKeeper のことを指している場合、代わりに名前付きパラメーターを使用します。

    typedef Timer(*timerType)();
    TimeKeeper aTimeKeeper(timerType aTimer);

すべて展開する

class ResourceType {
      int aMember;
    public:
      int getMember();
};

void getResource() {
    ResourceType aResource();
}

この例では、aResource はオブジェクトとして使用される可能性がありますが、宣言の構文は関数宣言を示しています。

修正 — オブジェクト宣言の {} を使用

1 つの修正方法 (C++11 以降) として、オブジェクト宣言の中かっこを使用することができます。

class ResourceType {
      int aMember;
    public:
      int getMember();
};

void getResource() {
    ResourceType aResource{};
}
class MemberType {};

class ResourceType {
      MemberType aMember;
    public:
      ResourceType(MemberType m) {aMember = m;}
      int getMember();
};

void getResource() {
    ResourceType aResource(MemberType()); 
}

この例では、aResourceMemberType 型の名前なしオブジェクトで初期化されたオブジェクトとして使用される可能性がありますが、宣言の構文は関数ポインター型の名前なしパラメーターをもつ関数を示しています。この関数ポインターは、引数がない MemberType 型の関数を指しています。

修正 — オブジェクト宣言の {} を使用

1 つの修正方法 (C++11 以降) として、オブジェクト宣言の中かっこを使用することができます。

class MemberType {};

class ResourceType {
      MemberType aMember;
    public:
      ResourceType(MemberType m) {aMember = m;}
      int getMember();
};

void getResource() {
    ResourceType aResource{MemberType()};
}
class Integer {
	int aMember;
public:
	Integer(int d) {aMember = d;}
	int getMember();
};

int aInt = 0;
void foo(){
	Integer aInteger(Integer(aInt));
}

この例では、aInteger は、名前なしオブジェクト Integer(aInt) (変数 aInt を使用して構築されるクラス Integer のオブジェクト) で構築されたオブジェクトである可能性があります。ただし、宣言の構文は aIntegerInteger 型の名前付きパラメーター aInt をもつ関数であることを示しています (余分なかっこは無視されます)。

修正 — オブジェクト宣言の {} を使用

1 つの修正方法 (C++11 以降) として、オブジェクト宣言の {} を使用することができます。

class Integer {
	int aMember;
public:
	Integer(int d) {aMember = d;}
	int getMember();
};

int aInt = 0;
void foo(){
	Integer aInteger(Integer{aInt});
}
修正 — 名前付きパラメーター宣言の余分なかっこを削除

aInteger が名前付きパラメーター aInt をもつ関数の場合、aInt を囲む余分な () を削除します。

class Integer {
	int aMember;
public:
	Integer(int d) {aMember = d;}
	int getMember();
};

int aInt = 0;
void foo(){
	Integer aInteger(Integer aInt);
}

結果情報

グループ: 適切な手法
言語: C++
既定値: オフ
コマンド ライン構文: MOST_VEXING_PARSE
影響度: Low

バージョン履歴

R2019a で導入