web-dev-qa-db-ja.com

クラス変数が渡されるクラスメソッドを作成するのは悪い考えですか?

これが私の意味です:

class MyClass {
    int arr1[100];
    int arr2[100];
    int len = 100;

    void add(int* x1, int* x2, int size) {
        for (int i = 0; i < size; i++) {
            x1[i] += x2[i];
        }
    }
};

int main() {
    MyClass myInstance;

    // Fill the arrays...

    myInstance.add(myInstance.arr1, myInstance.arr2, myInstance.len);
}

addはクラスメソッドであるため、必要なすべての変数にすでにアクセスできますが、これは悪い考えですか?これを行うべきか、すべきでない理由はありますか?

14
paper man

クラスには違う方法でやることがあるかもしれませんが、直接的な質問に答えるために、私の答えは

はい、それは悪い考えです

これの主な理由は、add関数に渡されるものを制御できないためです。確かにそれがメンバー配列の1つであることを期待していますが、誰かが100より小さいサイズの別の配列を渡す場合、または100を超える長さで渡す場合はどうなりますか?

バッファオーバーランの可能性が生じたことが起こります。そして、それは至る所で悪いことです。

さらに(私にとって)明白な質問に答えるには:

  1. Cスタイルの配列とC++を混在させています。私はC++の第一人者ではありませんが、C++には配列を処理するより優れた(より安全な)方法があることを知っています

  2. クラスにすでにメンバー変数がある場合、なぜそれらを渡す必要があるのですか?これは、建築上の問題です。

C++の経験が豊富な他の人(私は10年または15年前にその使用をやめました)は、問題を説明する説得力のある方法を持っている可能性があり、おそらくより多くの問題も思い付くでしょう。

33
Peter M

一部のクラス変数を使用してクラスメソッドを呼び出すことは、必ずしも悪いことではありません。しかし、クラスの外側からするのは非常に悪い考えであり、OOデザインの基本的な欠陥、つまり適切な encapsulation の欠如:

  • クラスを使用するコードでは、lenが配列の長さであることを認識し、一貫して使用する必要があります。これは、最小の知識の 原則 に反します。このようなクラスの内部の詳細への依存は、非常にエラーが発生しやすく、リスクを伴います。
  • これは、クラスの進化を非常に難しくします(たとえば、ある日、レガシー配列とlenをより新しい_std::vector<int>_に変更したい場合)。これも変更する必要があるためです。クラスを使用するすべてのコード。
  • コードのどの部分でも、ルールを守らずにパブリック変数を破壊することにより、MyClassオブジェクトに大混乱をもたらす可能性があります(いわゆる class invariants
  • 最後に、メソッドはパラメータとのみ連動し、他のクラス要素に依存しないため、実際にはクラスから独立しています。この種のメソッドは、クラス外の独立した関数である可能性があります。または、少なくとも静的メソッド。

コードをリファクタリングする必要があります。

  • クラス変数をprivateまたはprotectedにします。ただし、それを行わない正当な理由がない限り。
  • クラスで実行するアクションとしてパブリックメソッドを設計します(例:object.action(additional parameters for the action))。
  • このリファクタリングの後でも、クラス変数で呼び出す必要のあるクラスメソッドがいくつかある場合は、それらがパブリックメソッドをサポートするユーティリティ関数であることを確認した後、それらを保護またはプライベートにしてください。
48
Christophe

この設計の意図が、このクラスインスタンスから来ていないデータに対してこのメ​​ソッドを再利用できるようにすることである場合、それを static method として宣言することができます。 =。

静的メソッドは、クラスの特定のインスタンスに属していません。それはむしろクラス自体のメソッドです。静的メソッドはクラスの特定のインスタンスのコンテキストでは実行されないため、静的として宣言されているメンバー変数にのみアクセスできます。メソッドaddMyClassで宣言されているメンバー変数のいずれも参照していないことを考えると、静的として宣言されることをお勧めします。

ただし、他の回答で述べられている安全上の問題は有効です。両方の配列が少なくともlenであるかどうかをチェックしていません。モダンで堅牢なC++を作成する場合は、配列の使用を避けてください。クラス std::vector 可能な限り代わりに。通常の配列とは異なり、ベクトルは自身のサイズを知っています。したがって、自分でサイズを追跡する必要はありません。それらのメソッドのほとんど(すべてではありません!)も自動境界チェックを実行します。これにより、最後を超えて読み書きしたときに例外が発生することが保証されます。通常の配列アクセスは境界チェックを行わないため、せいぜいsegfaultが発生し、 悪用可能なバッファオーバーフローの脆弱性 を引き起こす可能性があります。

7
Philipp

クラスのメソッドは、クラス内のカプセル化されたデータを理想的に操作します。あなたの例では、addメソッドにパラメータがある理由はなく、単にクラスのプロパティを使用します。

1
Rik D

オブジェクト(の一部)をメンバー関数に渡すことは問題ありません。関数を実装するとき、あなたは常にエイリアスの可能性に注意してくださいとその結果をしなければなりません。

もちろん、コントラクトは、言語がそれをサポートしていても、ある種のエイリアスを未定義のままにするかもしれません。

顕著な例:

  • 自己割り当て。これは、おそらく高価で何もしないはずです。そのための一般的なケースを悲観しないでください。
    一方、自己移動割り当ては、あなたの顔で爆発するべきではありませんが、何もしない必要はありません。
  • コンテナー内の要素のコピーをコンテナーに挿入します。 v.Push_back(v[2]);のようなもの。
  • std::copy()は、宛先がソース内で開始しないことを前提としています。

しかし、あなたの例には異なる問題があります:

  • メンバー関数はその特権を使用せず、thisも参照しません。それは単に間違った場所にある無料のユーティリティ関数のように振る舞います。
  • クラスは、あらゆる種類のabstractionを提示しようとはしません。それと一致して、それはencapsulateその内部を試みたり、invariantsを維持したりしません。それは任意の要素のダムバッグです。

要約するとはい、この例は良くありません。ただし、メンバー関数がメンバーオブジェクトを渡されるためではありません。

0
Deduplicator

特にこれを行うことはいくつかの理由から悪い考えですが、それらすべてがあなたの質問に不可欠であるとは思えません:

  • クラス変数(定数ではなく実際の変数)を用意することは、可能な限り回避するためのものであり、絶対に必要かどうかについて真剣に考える必要があります。
  • これらのクラス変数への書き込みアクセスをすべての人に完全に公開したままにしておくことは、ほぼ例外なく悪いことです。
  • クラスメソッドは、クラスとは無関係になります。実際には、ある配列の値を別の配列の値に加算する関数です。この種の関数は、関数を持つ言語の関数である必要があり、関数を持たない言語の「偽クラス」(つまり、データやオブジェクトの動作がない関数のコレクション)の静的メソッドである必要があります。
  • または、これらのパラメーターを削除して実際のクラスメソッドに変換しますが、前述の理由により、依然として問題があります。
  • なぜint配列なのですか? vector<int>はあなたの人生を楽にします。
  • この例が本当に設計を表す場合は、データをクラスではなくオブジェクトに配置し、作成後にオブジェクトデータの値を変更する必要がない方法でこれらのオブジェクトのコンストラクターを実装することも検討してください。
0
Kafein

これは、C++に対する古典的な「C with classes」アプローチです。実際には、これは熟練したC++プログラマーが書くものではありません。 1つは、コンテナーライブラリを実装している場合を除いて、生のC配列を使用することは、一般的にはお勧めできません。

このようなものがより適切でしょう:

// Don't forget to compile with -std=c++17
#include <iostream>
using std::cout; // This style is less common, but I prefer it
using std::endl; // I'm sure it'll incite some strongly opinionated comments

#include <array>
using std::array;

#include <algorithm>

#include <vector>
using std::vector;


class MyClass {
public: // Begin lazy for brevity; don't do this
    std::array<int, 5> arr1 = {1, 2, 3, 4, 5};
    std::array<int, 5> arr2 = {10, 10, 10, 10, 10};
};

void elementwise_add_assign(
    std::array<int, 5>& lhs,
    const std::array<int, 5>& rhs
) {
    std::transform(
        lhs.begin(), lhs.end(), rhs.begin(),
        lhs.begin(),
        [](auto& l, const auto& r) -> int {
            return l + r;
        });
}

int main() {
    MyClass foo{};

    elementwise_add_assign(foo.arr1, foo.arr2);

    for(auto const& value: foo.arr1) {
        cout << value << endl; // arr1 values have been added to
    }

    for(auto const& value: foo.arr2) {
        cout << value << endl; // arr2 values remain unaltered
    }
}