1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C++のautoの誤用を検出したい

Last updated at Posted at 2022-11-19

はじめに

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++初心者以上でしょうし, 通常のプロジェクトでは禁止にしてしまってもいいと思います.

1
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?