42

API を作成していて、関数の 1 つがチャネルを表すパラメーターを取り、値が 0 から 15 の間であるとします。次のように記述できます。

void Func(unsigned char channel)
{
    if(channel < 0 || channel > 15)
    { // throw some exception }
    // do something
}

それとも、強く型付けされた言語である C++ を利用して、自分自身を型にしますか。

class CChannel
{
public:
    CChannel(unsigned char value) : m_Value(value)
    {
        if(channel < 0 || channel > 15)
        { // throw some exception }
    }
    operator unsigned char() { return m_Value; }
private:
    unsigned char m_Value;
}

私の関数は次のようになります。

void Func(const CChannel &channel)
{
    // No input checking required
    // do something
}

しかし、これは完全にやり過ぎですか?私はセルフドキュメンテーションと、それが言うとおりであることを保証するのが好きですが、追加のタイピングはもちろんのこと、そのようなオブジェクトの構築と破壊にお金を払う価値はありますか? あなたのコメントと代替案を教えてください。

4

14 に答える 14

60

この単純なアプローチが必要な場合は、特定のものに合わせて調整するのではなく、より多くの用途を利用できるように一般化してください。それなら、問題は「この特定のもののためにまったく新しいクラスを作るべきか」ではありません。しかし、「ユーティリティを使用する必要がありますか?」; 後者は常にイエスです。そして、ユーティリティは常に役に立ちます。

したがって、次のようなものを作成します。

template <typename T>
void check_range(const T& pX, const T& pMin, const T& pMax)
{
    if (pX < pMin || pX > pMax)
        throw std::out_of_range("check_range failed"); // or something else
}

これで、範囲をチェックするためのこの優れたユーティリティがすでに用意されています。チャンネルタイプがなくても、コードを使用することでコードをよりクリーンにすることができます。さらに進むことができます:

template <typename T, T Min, T Max>
class ranged_value
{
public:
    typedef T value_type;

    static const value_type minimum = Min;
    static const value_type maximum = Max;

    ranged_value(const value_type& pValue = value_type()) :
    mValue(pValue)
    {
        check_range(mValue, minimum, maximum);
    }

    const value_type& value(void) const
    {
        return mValue;
    }

    // arguably dangerous
    operator const value_type&(void) const
    {
        return mValue;
    }

private:
    value_type mValue;
};

これで、優れたユーティリティが手に入り、次のことができるようになりました。

typedef ranged_value<unsigned char, 0, 15> channel;

void foo(const channel& pChannel);

また、他のシナリオでも再利用できます。"checked_ranges.hpp"すべてをファイルに貼り付けて、必要なときにいつでも使用できます。抽象化を行うことは決して悪いことではなく、ユーティリティが周りにあることは有害ではありません。

また、オーバーヘッドについて心配する必要はありません。クラスの作成は、とにかく実行するのと同じコードを実行するだけです。さらに、クリーンなコードが他の何よりも優先されます。パフォーマンスは最後の懸念事項です。完了したら、プロファイラーを使用して、遅い部分がどこにあるかを(推測ではなく)測定できます。

于 2010-07-05T19:44:26.473 に答える
27

はい、そのアイデアは価値がありますが、(IMO)整数の範囲ごとに完全な個別のクラスを作成することは無意味です。限られた範囲の整数を必要とする十分な状況に遭遇したので、次の目的でテンプレートを作成しました。

template <class T, T lower, T upper>
class bounded { 
    T val;
    void assure_range(T v) {
        if ( v < lower || upper <= v)
            throw std::range_error("Value out of range");
    }
public:
    bounded &operator=(T v) { 
        assure_range(v);
        val = v;
        return *this;
    }

    bounded(T const &v=T()) {
        assure_range(v);
        val = v;
    }

    operator T() { return val; }
};

それを使用すると、次のようになります。

bounded<unsigned, 0, 16> channel;

もちろん、これよりも複雑にすることもできますが、この単純なものでも、状況の約90%をかなりうまく処理できます。

于 2010-07-05T19:49:47.913 に答える
14

いいえ、やり過ぎではありません。常に抽象化をクラスとして表現するようにしてください。これを行う理由は無数にあり、オーバーヘッドは最小限です。ただし、CChannel ではなく Channel クラスと呼びます。

于 2010-07-05T19:37:26.753 に答える
11

これまでのところ、列挙型について誰も言及していないとは信じられません。防弾保護は提供されませんが、単純な整数データ型よりも優れています。

于 2010-07-05T19:51:02.753 に答える
6

「CChannel」オブジェクトを構築するときに例外をスローするか、制約を必要とするメソッドの入り口で例外をスローするかは、ほとんど違いはありません。どちらの場合も、実行時にアサーションを行っているということは、型システムが実際に何の役にも立たないということですよね?

厳密に型指定された言語でどこまでできるかを知りたい場合、答えは「非常に遠いですが、C++ ではそうではありません」です。「このメソッドは 0 から 15 の間の数値でのみ呼び出される可能性があります」などの制約を静的に適用するために必要な種類の権限には、依存型と呼ばれるもの、つまり、値に依存する型が必要です。

この概念を疑似 C++ 構文 (C++ に依存型があるふりをする) に入れるには、次のように記述します。

void Func(unsigned char channel, IsBetween<0, channel, 15> proof) {
    ...
}

タイプではなくIsBetweenによってパラメータ化されていることに注意してください。プログラムでこの関数を呼び出すには、コンパイラに 2 番目の引数 を指定する必要があります。この引数の型は でなければなりません。つまり、コンパイル時に 0 ~ 15であることを証明する必要があります。値がそれらの命題の証明である命題を表す型のこの考えは、カリー・ハワード対応と呼ばれます。proofIsBetween<0, channel, 15>channel

もちろん、そのようなことを証明するのは難しい場合があります。問題のドメインによっては、コストと利益の比率が、コードの実行時チェックを平手打ちするだけの場合に簡単に有利になる可能性があります。

于 2010-07-30T10:43:57.470 に答える
6

operator unsigned char()特にアクセサーはやり過ぎのように見えます。データをカプセル化しているわけではありません。明らかなことをより複雑にし、おそらくエラーを起こしやすくしています。

あなたのようなデータ型Channelは通常、より抽象化されたものの一部です。

したがって、クラスでその型を使用する場合は、の本体ChannelSwitcherでコメント付きの typedef を使用できます(おそらく、typedef は になります)。ChannelSwitcherpublic

// Currently used channel type
typedef unsigned char Channel;
于 2010-07-05T19:34:28.233 に答える
4

何かがやり過ぎであるかどうかは、多くの場合、さまざまな要因に依存します。ある状況でやり過ぎかもしれないことは、別の状況ではそうではないかもしれません。

すべてがチャネルを受け入れ、すべてが同じ範囲チェックを実行する必要がある多くの異なる関数がある場合、このケースはやり過ぎではないかもしれません。Channelクラスは、コードの重複を回避し、関数の可読性も向上させます(CChannelの代わりにクラスにChannelという名前を付けるように-Neil B.が正しい)。

範囲が十分に小さい場合は、代わりに入力の列挙型を定義します。

于 2010-07-05T19:45:41.073 に答える
1

16の異なるチャネルの定数を追加し、特定の値のチャネルをフェッチする(または範囲外の場合は例外をスローする)静的メソッドを追加すると、メソッド呼び出しごとにオブジェクトを作成する追加のオーバーヘッドなしで機能します。

このコードがどのように使用されるかを知らなければ、それがやり過ぎであるかどうか、または使用するのが楽しいかどうかを判断するのは難しいです。自分で試してみてください-charとtypesafeクラスの両方のアプローチを使用していくつかのテストケースを書いてください-そしてあなたが好きなものを見てください。いくつかのテストケースを書いた後でそれにうんざりした場合は、おそらく避けるのが最善ですが、このアプローチが気に入った場合は、キーパーである可能性があります。

これが多くの人に使用されるAPIである場合、おそらくAPIドメインをよく知っているので、レビューを開くと貴重なフィードバックが得られる可能性があります。

于 2010-07-05T19:45:02.917 に答える
1

チャネルの例は難しいものです。

  • 最初は、Pascal や Ada に見られるような、範囲が限定された単純な整数型のように見えます。C++ ではこれを言う方法はありませんが、enum で十分です。

  • よく見ると、変更される可能性のある設計上の決定の 1 つになる可能性がありますか? 周波数で「チャネル」を参照していただけますか? コールレター(WGBH、入ってください)で?ネットワークで?

多くはあなたの計画に依存します。API の主な目的は何ですか? コストモデルは?チャネルは非常に頻繁に作成されますか (そうではないと思います)?

少し違った見方をするために、台無しにすることのコストを見てみましょう。

  • として担当者を公開しintます。クライアントは大量のコードを記述し、インターフェイスが尊重されるか、ライブラリがアサーション エラーで停止します。チャネルの作成は非常に安価です。しかし、やり方を変更する必要がある場合、「後方バグ互換性」が失われ、ずさんなクライアントの作成者を悩ませます。

  • あなたはそれを抽象的に保ちます。誰もが抽象化を使用する必要があり (それほど悪くはありません)、誰もが API の変更に対して将来的に保証されています。下位互換性を維持するのは簡単なことです。しかし、チャネルの作成にはコストがかかります。さらに悪いことに、API は、チャネルを破棄しても安全な時期と、決定と破棄の責任者を慎重に指定する必要があります。最悪のシナリオは、チャネルの作成/破棄が大きなメモリ リークやその他のパフォーマンス障害につながることです。この場合、列挙型にフォールバックします。

私はずさんなプログラマーです。自分の仕事の場合は、列挙型を使用して、設計上の決定が変更された場合にコストを消費します。しかし、この API がクライアントとして他の多くのプログラマーに渡される場合、私は抽象化を使用します。


明らかに私は道徳的相対主義者です。

于 2010-07-06T03:19:55.867 に答える
1

私の意見では、あなたが提案していることは大きなオーバーヘッドではないと思いますが、私にとっては、入力を節約し、0..15 以外のものは未定義であることをドキュメントに記載し、assert() を使用することを好みます。関数内でデバッグ ビルドのエラーをキャッチします。仕様に多くの未定義の動作が含まれる C++ 言語プログラミングに既に慣れているプログラマーにとって、追加された複雑さはより多くの保護を提供するとは思いません。

于 2010-07-05T20:31:04.367 に答える
1

あなたは選択をしなければなりません。ここに特効薬はありません。

パフォーマンス

パフォーマンスの観点からは、オーバーヘッドは、たとえあったとしてもそれほど多くはありません。(CPUサイクルを数えなければならない場合を除きます)したがって、おそらくこれは決定要因にはなりません。

シンプルさ/使いやすさなど

API をシンプルで理解しやすく、習得しやすいものにします。数字/列挙型/クラスがAPIユーザーにとってより簡単かどうかを知る/決定する必要があります

保守性

  1. チャネルタイプが近い将来整数になることが確実な場合は、抽象化なしで行きます(列挙型の使用を検討してください)

  2. 境界値のユースケースが多い場合は、テンプレートの使用を検討してください (Jerry)

  3. あなたが考えるなら、チャネルはメソッドを持っている可能性があり、それをクラスにすることができます。

コーディング作業 は 1 回限りです。だから常にメンテナンスを考えてください。

于 2010-07-05T20:38:49.387 に答える
0

私はあなたの最初のアプローチに投票します。これは、理解、維持、および拡張がよりシンプルで簡単であり、API を再実装/翻訳/移植/などする必要がある場合に、他の言語に直接マップする可能性が高いためです。

于 2010-07-05T20:48:38.327 に答える
0

これは私の友人の抽象化です!オブジェクトを操作する方が常にきれいです

于 2010-07-05T20:55:25.413 に答える