2

これは私が頻繁に受ける質問であり、最終的には好みのスタイルについて人々の意見を聞きたい.

コンストラクターでパラメーターまたはメンバーを (読み取り専用の目的で) 使用する方が良い/推奨される方法はありますか? たとえば、次の単純なベクトル クラスでは、次のようになります。

#include <iostream>
#include <array>

class SimpleDoubleVector {
private:
  double * _data;
  std::size_t _size;
public:
  SimpleDoubleVector(double * data, std::size_t size) :
  _size(size) {
    _data = new double[size];
    for (int k=0; k<size; ++k)
      _data[k] = data[k];
  }
  ~SimpleDoubleVector() {
    delete[] _data;
  }
};

したほうがいいですか

  1. sizeコンストラクター全体で使用する(示されているように)または
  2. 最初に割り当て/初期化_sizeしてから使用します_size

考えられる影響:

どちらが読みやすいですか?

どちらがより優れたパフォーマンスを発揮しますか (または、コピーの伝播により両方が同等になりますか)? 直観的には、パラメーターからの読み取りがより効率的であるように感じます。これは、パラメーターに書き込まれることがないためであり、依存関係グラフがより単純になります。

これは衒学的に思えるかもしれませんが、非常に頻繁に出てくるので、最善の方法を分析したいと思います(または、少なくとも長所と短所が何であるかをよりよく理解したいと思います)。

4

4 に答える 4

2

次の 2 つの理由から、コンストラクター全体で常に (可能な場合) パラメーターを使用します。

1) 外部入力からオブジェクトの状態を初期化しています。パラメータを使用すると、この外部データの使用が強調されます。

2)イニシャライザリストをより広範囲に使用すると、初期化される前にクラスメンバーを使用するさまざまな問題が防止されます(初期化順序はコンストラクタのイニシャライザ順序ではなく、メンバー順序によって指定されるため)。

あるものと他のものとでかなりの違いを生むようなパフォーマンス上の理由は思いつかないので、プロファイラーがそれを変更すると大幅な改善が得られると私に言った場合にのみ、別のアプローチを選択します。

于 2012-06-27T04:59:08.097 に答える
2

意味的には、多くの場合、メンバー変数よりもローカル変数 (したがってパラメーター) が優先されます。このやや捏造された例を見てみましょう:

class Complex {
    float real_;
    float imag_;

public:
    Complex& operator*=(const Complex& that) {
        real_ = real_ * that.real_ - imag_ * that.imag_;
        imag_ = imag_ * that.real_ + real_ * that.imag_;
    }
};

real_最初の行の の変更が 2 番目の行の値を変更したことに気付くまでは、一見して問題ありませんreal_。それをキャッチしてオリジナルをローカル変数に格納したとしても、演算子の左側と右側がエイリアス化され、最初の行の の変更が意図せず変更さreal_れる の場合になる可能性があります。 2行目。つまり、メンバー変数への変更には、ローカル変数への変更では起こらない副作用を引き起こす可能性があります。c *= creal_that.real_

速度に関しては、妥当なコンパイラであれば、この 2 つが同一であることがわかります。パラメーターを再利用すると、不合理なコンパイラーがより良いコードを生成する可能性があります。これは、パラメーターが既にローカルにあり、コンパイラーが認識できるコード以外はその値を変更できないことを確実に認識しているためです。また、やや複雑なケースでは、優れたコンパイラであっても、次のようなケースではより悪い出力が生成される可能性があることにも注意してください。

void MyClass::foo(int value, MyClass* child) {
    value_ = value;
    for (int i = 0; i < value_; ++i) {
        if (child) child->value_ = i;
        bar(i, child);
    }
}

thisこの関数は、とchildが異なるポインターであることを保証する方法はまったくありません。value_への割り当てが変更された可能性があるため、ループ反復間でレジスタにchild->value_保持することはできませんthis->value_。この場合、優れたコンパイラでさえ、パラメータを使用することを望んでいます。

読みやすさに関しては、メンバー名の前後のアンダースコア (またはm_、さらに言えば ) が判読不能になると思われる場合、なぜその表記法を使用しているのですか? コンストラクタ本体と通常の関数本体との間の一貫性は、間違いなく望ましいものです。したがって、セマンティクスが関数の実行中にメンバー変数をローカル変数にプルすることを奨励している場合は、コンストラクターでもそれを行うことを主張します (パラメーターを使用するだけです)。ただし、そのような規則が他のメンバー関数で使用されていない場合は、コンストラクターでも使用しないでください。コンパイラーに処理させてください。

于 2012-06-27T04:35:13.743 に答える
1

問題が読みやすさである場合、答えは初期化子リストでなければなりません。が最初にリストされているため_data、この問題は一種の強制的なものです。

    SimpleDoubleVector(double * data, std::size_t size)
        : _data(std::copy(data, data+size, new double[size])),
          _size(size)
        {}

が最初にリストされている場合_sizeは選択肢がありますが、この場合はパラメーターを使用することを選択します_。. std::copyパフォーマンスの違いはごくわずかだと思います。

コンストラクターの本体で初期化を行う必要がある場合、つまりパラメーターの名前とデータ メンバー名が 1 対 1 で対応している場合、同じ理由を使用します。データ メンバーがパラメーターの何らかの計算で初期化された場合、他のデータ メンバーの初期化に役立つ場合は、明らかにコードで計算された値を使用する必要があります。複雑な初期化がある場合、その初期化を別の関数に配置すると便利なことがよくあります。これは、複数のコンストラクターを想定しています。この関数は、コンストラクターと初期化関数の間で渡されるパラメーターを最小限に抑えるために、初期化されたデータ メンバーを利用するように記述できます。

    SimpleDoubleVector(double * data, std::size_t size) {
        _size = size;
        initialize_data(data);
    }

    SimpleDoubleVector(std::size_t size) {
        _size = size;
        initialize_data();
    }

    double * initialize_data(double * data = 0) {
        _data = new double[_size];
        if (data) {
            for (std::size_t k = 0; k < _size; ++k) {
                _data[k] = data[k];
            }
        }
    }
于 2012-06-27T04:07:19.103 に答える
1

私は次のようにクラスを作ります:

class SimpleDoubleVector {
private:
  std::size_t _size; // Make sure this is declared first!!
  double * _data;
public:
  SimpleDoubleVector(double * data, std::size_t size) : 
      _size(size), data(new double[size]) // Use initialization lists
  {
    for (int k = 0; k < _size; ++k) // Could eliminate all this with std::vector
      _data[k] = data[k];
  }
  ~SimpleDoubleVector() {
    delete[] _data;
  }
};

もちろん、これがすべてのコードではありません。リソースを管理しているため、3 のルール (または C++11 では 5 など) を実装する必要があります。ただし、いくつかの指針:

  1. コンストラクターの本体で行う_size = size;と、初期化を行うのではなく、代入を行うことになります。そのため、初期化リストを使用する必要があります (もちろん、組み込み型の場合、これは事実上同じことですが、意図が違うと主張する)。

  2. コンストラクターに渡される引数は、例のメンバー変数を初期化するためのものです。初期化以外の目的で使用しないでください。

  3. std::vector<double>おそらくorを使用したほうがよいでしょうがstd::array<double>、それは質問とは無関係であると確信しています。

また、依存関係グラフがこの質問にどのように関連しているのかわかりません。

_(個人的なメモ:メンバー変数の先頭にスタイルを追加するのは好きではありません)

于 2012-06-27T04:07:36.543 に答える