[[nodiscard]]の使い方と活用場面(C++17以降)
問題の本質
C++17で導入された[[nodiscard]]
属性について、特に「なぜクラスのconstメソッドにこの属性を付ける必要があるのか」「コンパイラが戻り値を無視するとは具体的にどういう状況か」という疑問が生じます。例として以下のコードを考えます:
cpp
class Test {
public:
[[nodiscard]] int f(int a, int b) const {
return a + b;
}
};
一般的な誤解として「呼び出し側が戻り値を処理する限り問題ないのでは?」と思うかもしれません。しかしこの属性が防ぐのは、プログラマが意図せず戻り値を無視してしまうバグです。具体的には:
cpp
Test obj;
obj.f(10, 20); // 警告発生!戻り値を無視している
解決策:[[nodiscard]]の適切な使い方
[[nodiscard]]
は主に以下の3つのケースで効果を発揮します:
1. 計算結果だけが意味を持つ関数(副作用のない関数)
cpp
[[nodiscard]] double calculateArea(double radius) {
return 3.14 * radius * radius;
}
// 使用例
calculateArea(5.0); // 警告: 計算結果を無視している
2. 新しいオブジェクトを返すメソッド(元のオブジェクトを変更しない)
cpp
class DateTime {
public:
[[nodiscard]] DateTime addDays(int days) const;
};
// 誤った使用例
DateTime dt;
dt.addDays(1); // 警告: 新しいDateTimeオブジェクトを無視!
3. エラーコードやリソースハンドルを返す関数
cpp
[[nodiscard]] FileHandle openFile(const std::string& path) {
// ファイルオープン処理
}
// 危険な例
openFile("data.txt"); // 警告: ファイルハンドルを無視→リソースリークの可能性
実際の警告例
コンパイラは[[nodiscard]]
関数の戻り値無視を検出すると警告を出します:
cpp
class List {
public:
[[nodiscard]] List reverse() const;
};
List myList{1, 2, 3};
myList.reverse(); // 警告: nodiscard属性付き関数の戻り値を破棄
std::cout << myList; // 元のリストが表示される(逆順ではない)
正しい使い方
cpp
List reversed = myList.reverse(); // 戻り値を受け取る
std::cout << reversed; // 逆順リストが表示される
なぜメンバ関数に必要か?
IDE(CLionなど)がconstメソッドに[[nodiscard]]
を提案する理由:
- 意図が明確化
関数が状態変更ではなく「結果を返すだけ」であることを示す - バグ予防
オブジェクトの状態が変更されないため、無視はほぼ確実に誤り - 自己文書化
コードを見ただけで「戻り値を処理すべき」と分かる
適用すべきではないケース
すべての関数に付けるべきではありません:
cpp
// 副作用が目的の関数(戻り値の無視が許容される例)
void saveToDatabase(); // 戻り値ないので不要
void getVersion(); // バージョン出力だけが目的
注意点
static_cast<void>
で明示的に無視した場合は警告が抑制されます
cpp
(void)calculateArea(10.0); // 警告なし(意図的な無視を示す)
- C++20以降ではエラーメッセージのカスタマイズ機能も追加されています
ベストプラクティス
- リソース管理関数には必須で適用
- 状態変更せず結果を返すメンバ関数に積極適用
- コンストラクタにも適用可能(C++20以降)
- 警告が煩わしい場合は「無視」ではなく「設計の見直し」を
[[nodiscard]]
は現代C++における重要なコードセーフティ機能です。適切な場面で活用することで、戻り値の誤った扱いによるバグをコンパイル時に防止でき、意図が明確な自己文書化コードの作成に貢献します。