クラス変数を渡すクラスメソッドを作成したのは悪い考えですか?


14

ここに私が意味するものがあります:

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クラスメソッドなので、必要なすべての変数に既にアクセスできますが、これは悪い考えですか?これを行うべき、またはすべきでない理由はありますか?


13
あなたがこれをやりたいと思うなら、それは悪いデザインを暗示しています。クラスのプロパティはおそらく別の場所に属します。
bitsoflogic

11
スニペットが小さすぎます。このレベルの混合抽象化は、このサイズのスニペットでは発生しません。また、設計上の考慮事項に関する適切なテキストを使用して、これを修正することもできます。
ジョシュア

16
なぜあなたがこれをするのか、私には正直わかりません。何が得られますか?add内部を直接操作する引数なしのメソッドを用意しないのはなぜですか?ただ、なぜ?
イアン・ケンプ

2
@IanKempまたは、add引数を取るがクラスの一部として存在しないメソッドを持っています。2つの配列を一緒に追加するための単なる関数です。
ケビン

addメソッドは常に arr1とarr2を追加しますか、それとも何かに何かを追加するために使用できますか?
user253751

回答:


33

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

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

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

何が起こるかは、バッファオーバーランの可能性を作成したことです。そして、それは周りの悪いことです。

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

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

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

C ++の経験が豊富な他の人(10年または15年前に使用をやめた人)は、問題を説明するより雄弁な方法を持っているかもしれません。


実際、vector<int>より適切です。その場合、長さは役に立ちません。あるいはconst int len、可変長配列はC ++標準の一部ではないため(一部のコンパイラはCのオプション機能であるため、これをサポートしています)。
クリストフ

5
@Christophe Authorは固定サイズの配列を使用しstd::arrayていましたが、パラメータに渡すときに減衰せず、サイズを取得するより便利な方法があり、コピー可能で、オプションの境界チェックでアクセスできますが、オーバーヘッドはありませんCスタイルの配列。
キュービック

1
@Cubic:任意の固定サイズ(100など)で配列を宣言するコードを見るときはいつでも、著者はこのナンセンスの意味について全く知らない初心者であり、彼/彼女を推薦することは良い考えですこのデザインを可変サイズの何かに変更します。
Doc Brown

配列は問題ありません。
ライトネスレースとモニカ

...私は何を意味するか理解していなかった人のために、また参照のゼロワンインフィニティルール
ドク・ブラウン

48

一部のクラス変数を使用してクラスメソッドを呼び出すことは、必ずしも悪いことではありません。しかし、クラス外からそうすることは非常に悪い考えであり、オブジェクト指向設計の根本的な欠陥、つまり適切なカプセル化の欠如を示唆しています:

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

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

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

7

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

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

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


1

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


0

オブジェクト(の一部)をメンバー関数に渡すことは問題ありません。あなたの機能を実現するときは、必ずしなければならない可能エイリアシングを認識すること、およびその結果。

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

顕著な例:

  • 自己割り当て。これは、おそらく高価な、ノーオペレーションでなければなりません。その一般的なケースを悲観しないでください。
    一方、自己移動の割り当ては、あなたの顔で爆発することはありませんが、無操作である必要はありません。
  • コンテナ内の要素のコピーをコンテナに挿入します。のようなものv.push_back(v[2]);
  • std::copy() 宛先がソース内で開始しないと仮定します。

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

  • メンバー関数は、その特権を使用せず、参照もしませんthis。単に間違った場所にある無料のユーティリティ関数のように機能します。
  • あなたのクラスはどんな種類の抽象化も提示しようとしません。それに沿って、その内部をカプセル化しようとせず、不変式を維持しようとしません。これは、任意の要素の愚かなバッグです。

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


0

具体的にこれを行うことはいくつかの正当な理由から悪い考えですが、それらすべてがあなたの質問に不可欠であるとは確信していません:

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

0

これは、C ++への古典的な「クラスを持つC」アプローチです。実際には、これはベテランのC ++プログラマーが書くものではありません。たとえば、コンテナライブラリを実装している場合を除き、生の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
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.