はじめに
C++に型推論auto
が追加されて10年あまり, 間違った使い方をたくさん見てきました.
auto
の使用に関しては, Epicのコーディング規約に, 私にしてはめずらしく 完全に同意します. 要約するほどの分量ではないですが, リンクを踏みたくない人のために
- コードの読み手へ型を明らかにすること
- この理由は書かれていません
- 私は型はプログラムを説明するものだと思っています
- "IDEに推論結果のポップアップ表示があるから", という言い訳は認められません. VCS, merge, diff, レビューツールなどでサポートされないからです.
- この理由は書かれていません
-
auto
の使用が認められる例外- 型名が冗長になりがちなiterator
- lambda, templateでの, 使用が避けられない場合
-
auto
を使用する場合, 常に正しくconst
,&
, または*
を使うこと
テストコード
もっと複雑でないと検証にならないと思いますが, コンパイル単位を分けるぐらいはやってみます.
SugoiDekai.h
#ifndef INC_SUGOIDEKAI_H_
#define INC_SUGOIDEKAI_H_
#include <cstdint>
namespace check
{
struct SugoiDekai
{
static constexpr uint32_t Size = 1024;
void zeros();
void fill();
void print();
uint8_t data_[Size];
};
void print(SugoiDekai data);
} // namespace check
#endif // INC_SUGOIDEKAI_H_
SugoiDekai.cpp
#include "SugoiDekai.h"
#include <chrono>
#include <cstring>
#include <iostream>
#include <random>
namespace check
{
void SugoiDekai::zeros()
{
::memset(data_, 0, sizeof(uint8_t) * Size);
}
void SugoiDekai::fill()
{
std::random_device device;
std::minstd_rand engine(device());
for(uint32_t i = 0; i < Size; ++i) {
data_[i] = static_cast<uint8_t>(engine());
}
}
void SugoiDekai::print()
{
for(uint32_t i = 0; i < Size; ++i) {
if(0 == (i % 1024)) {
std::cout << static_cast<uint32_t>(data_[i]);
}
}
std::cout << std::endl;
}
void print(SugoiDekai data)
{
data.print();
}
} // namespace check
型名を明示した場合も含めて, 人が見ると明らかな論理的不具合を2パターン, パフォーマンス的によろしくない1パターン(auto
と関係ないですが)を用意しました.
#include "SugoiDekai.h"
#include <vector>
int main(void)
{
using namespace check;
static constexpr uint32_t NumSamples = 1000;
std::vector<SugoiDekai> data;
data.resize(NumSamples);
{ // Initialize
for(auto& d: data) {
d.zeros();
}
}
{ // Bad usage 1 --- case 1
for(auto d: data) {
d.fill();
}
}
{ // Bad usage 2 --- case 2
for(size_t i = 0; i < data.size(); ++i) {
auto d = data[i];
d.fill();
}
}
{ // Bad usage 3 --- case 3
for(const auto& d: data) {
print(d);
}
}
{ // Bad usage 4 --- case 4
for(SugoiDekai d: data) {
d.fill();
}
}
{ // Bad usage 5 --- case 5
for(size_t i = 0; i < data.size(); ++i) {
SugoiDekai d = data[i];
d.fill();
}
}
{ // Bad usage 6 --- case 6
for(const SugoiDekai& d: data) {
print(d);
}
}
return 0;
}
コンパイルオプションは次のCMakeLists.txtを参照のこと.
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
set(PROJECT_NAME check)
project(${PROJECT_NAME})
set(PROJECT_VERSION 1.0.0)
set(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include_directories(AFTER ${PROJECT_ROOT})
set(HEADERS "SugoiDekai.h")
set(SOURCES "SugoiDekai.cpp;main.cpp")
source_group("include" FILES ${HEADERS})
source_group("src" FILES ${SOURCES})
add_executable(${PROJECT_NAME} ${HEADERS} ${SOURCES})
set_target_properties(${PROJECT_NAME} PROPERTIES
OUTPUT_NAME_DEBUG "${PROJECT_NAME}d" OUTPUT_NAME_RELEASE "${PROJECT_NAME}")
if(MSVC)
set(DEFAULT_CXX_FLAGS "/DWIN32 /D_WINDOWS /D_UNICODE /DUNICODE /W4 /WX- /nologo /fp:precise /arch:AVX /Oi /Zc:wchar_t /TP /Gd")
if(MSVC_VERSION VERSION_LESS_EQUAL "1900")
set(DEFAULT_CXX_FLAGS "${DEFAULT_CXX_FLAGS} /Zc:__cplusplus /std:c++latest")
else()
set(DEFAULT_CXX_FLAGS "${DEFAULT_CXX_FLAGS} /Zc:__cplusplus /std:c++17")
endif()
set(CMAKE_CXX_FLAGS "${DEFAULT_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Zi /Ob0 /Od /RTC1 /Gy /GR- /GS /Gm- /EHsc")
set(CMAKE_CXX_FLAGS_RELEASE "/MD /O2 /Oi /GL /GR- /DNDEBUG /EHsc-")
elseif(UNIX)
set(DEFAULT_CXX_FLAGS "-Wall -Wextra -O2 -std=c++17 -march=x86-64-v3 -fno-exceptions")
elseif(XCODE)
endif()
解析ツール
Visual Studio 静的解析
何も検出されませんでした.
clang-tidy
performance-for-range-copy
が仕事をしてくれそうだったのですが, 何も検出されませんでした.
cppcheck
何も検出されませんでした.
Coverity
WIP
オンラインスキャンがうまくいっていないため.
Error details: invalid literal for int() with base 10: "tail: cannot open '/opt/workspace/username_projectname/cov-int/build-log.txt' for reading: No such file or directory"
case 3, 6で警告が出ることは分かっています.
まとめ
静的解析での検出は期待できないかもと思います. 全文検索のパターンマッチの方がよさそうな気がしてきました.
auto
をどうしても使わなければならない人はC++初心者以上でしょうし, 通常のプロジェクトでは禁止にしてしまってもいいと思います.