メインコンテンツ

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

計算量の多い値渡し

パラメーターのコピーの計算量が多くなる場合がある

説明

この欠陥は、パラメーターを参照やポインターではなく値で渡すが、パラメーターが変更されておらず、以下のどちらかの場合に発生します。

  • パラメーターが非トリビアル コピー可能な型である。非トリビアル コピー可能な型の詳細については、is_trivially_copyable を参照してください。

  • パラメーターが特定のサイズを上回るトリビアル コピー可能な型である。たとえば、2 * sizeof(void *) を上回るオブジェクトは、参照渡しやポインター渡しより値渡しの方が計算量は多くなります。

Polyspace® は、パラメーターが const に宣言されていなくても、前述の条件を満たしている変更されていないパラメーターにフラグを設定します。

Polyspaceは、以下の場合に欠陥を発生させません。

  • 値渡しのパラメーターが移動のみの型である。たとえば、std::unique_ptr は、移動元にできますが、コピーすることはできません。

  • 値渡しのパラメーターが変更された。

  • パラメーターが、派生クラスのオーバーライド元のメソッド virtual に値渡しされる。

    この場合、この欠陥を修正するには基底クラス メソッドのシグネチャの変更が必要となることがありますが、派生クラスのオーナーはこの変更を実行できない可能性があります。

  • パラメーターが基底クラスの virtual メソッドに値渡しされ、パラメーターは非 const であった。

    この場合、派生クラスにそのパラメーターを変更するオーバーライド元のメソッドがあり、基底クラスのメソッドに修正が適用された場合に、オーバーライド元のメソッドが正しく更新されない可能性があります。

    パラメーターが const の場合は、派生クラスのオーバーライド元のメソッドはパラメーターを変更しないため、基底クラスで修正を適用しても問題ありません。この場合、Polyspace は [計算量の多い値渡し] 欠陥を報告します。

たとえば、次のコードでは Polyspace は、以下の関数とメソッドの欠陥を報告しません。

  • 関数 offset() (パラメーターが変更されるため)。

  • 関数 moveOnly() (パラメーターが移動のみの型であるため)。

  • メソッド D::parse_M および D::print_M (派生クラスのオーバーライド元のメソッド virtual であるため)。

  • メソッド Base::print_M (非 const パラメーターがある基底クラスの virtual メソッドであるため)。

Polyspace は Base::parse_M について欠陥を報告します。これは、そのパラメーターが const であり、非トリビアル コピー可能であるためです。

#include <string>
#include <memory>
#include <iostream>

typedef struct Buffer
{
    unsigned char bytes[20];
    int index;
} Buffer;

void offset(Buffer modifiedBuffer)
{
    ++modifiedBuffer.index;
}

void moveOnly(std::unique_ptr<Buffer> move_only_param);

// virtual methods example

class Base
{
public:
    virtual void parse_M(const std::string msg);
    virtual void print_M(std::string msg)
    {
        std::cout << "Base message: " << msg << "\n";
    }
};

class D : public Base
{
public:
    void parse_M(const std::string msg) override;
    void print_M(std::string msg) override
    {
        std::cout << "Derived message: " << msg << "\n";
    }
};

リスク

パラメーターを値で渡すと、パラメーターのコピーが作成されますが、パラメーターのコピーの計算量が多い場合は非効率になります。参照またはポインターで渡すつもりでも、関数シグネチャに const& または const* を入れ忘れて、非効率なバージョンの関数を実行する可能性があります。

修正方法

C コードの場合はパラメーターを const ポインター (const*) に変換し、C++ コードの場合は const 参照 (const&) に変換します。

パフォーマンスの改善の程度は、使用しているコンパイラ、ライブラリ実装、環境によって異なる可能性があります。

すべて展開する

#include<string>

class Player
{
public:
    void setName(std::string const str)
    {
        name = str;
    }
    void setRank(size_t r)
    {
        rank = r;
    }
    // getter functions implementation
private:
    std::string name;
    size_t rank;

};

この例では、Polyspace は、値渡しとなり、計算量の多いコピーになる setter 関数 setName のパラメーターにフラグを設定します。std::string 型はトリビアル コピー可能ではありません。setRank の値渡しのパラメーターにはフラグが設定されません。これは、size_t が小さなトリビアル コピー可能なタイプだからです。

修正 — std::string パラメーターを const 参照で渡す

非効率なコピー操作を避けるには、const& を使用してパラメーターを渡します。

#include<string>

class Player
{
public:
    void set_name(std::string const& s)
    {
        name = s;
    }
    void set_rank(size_t r)
    {
        rank = r;
    }
    // getter functions implementation
private:
    std::string name;
    size_t rank;

};
#include<stdio.h>
#include<string.h>

typedef struct _Player {
    char name[50];
    size_t rank;
} Player;


void printPlayer(Player const player)
{

    printf("Player name: %s\n", player.name);
    printf("Player rank: %zu\n", player.rank);
}

この例では、Polyspace は、値渡しとなり、計算量の多いコピーになる printPlayer のパラメーターにフラグを設定します。

修正 — const ポインターで大きい構造体を渡す

非効率なコピー操作を避けるには、const* を使用してパラメーターを渡してから、適切な表記を使用して構造体要素を読み取ります。

#include<stdio.h>
#include<string.h>

typedef struct _Player {
    char name[50];
    size_t rank;
} Player;


void printPlayer(Player const* player)
{

    printf("Player name: %s\n", player->name);
    printf("Player rank: %zu\n", player->rank);
}

結果情報

グループ: パフォーマンス
言語: C | C++
既定値: オフ
コマンド ライン構文: EXPENSIVE_PASS_BY_VALUE
影響度: Medium

バージョン履歴

R2020b で導入

すべて展開する