10

この状況は、コンストラクターのパラメーターに制約を作成する方法に関連していますが、少し異なります。

デフォルトで構築できないメンバーを初期化したいが、構築する前に制約をチェックする必要がある。

例:

(これは単なる例であることに注意してください。この特定の状況で代わりに符号なし整数を使用する必要があるかどうかは議論の余地がありますが、問題はコンストラクターをチェックインする一般的なケースに関するものです)

次のクラスがあります。

class Buffer {
public:
    Buffer() = delete;
    Buffer(int size) noexcept;
};
....


class RenderTarget {
public:
    ....
private:
    int width_, height_;
    Buffer surface_;
};

コンストラクターは、整数引数の有効性をチェックする必要があります。

RenderTarget::RenderTarget(int width, int height) :
    width_(width), height_(height),
    surface_(width_*height)
{
    if (width_<0 || height_<0)
        throw std::logic_error("Crizzle id boom shackalack");
}

Bufferhowにはデフォルトのコンストラクタがなく、実際のコンストラクタはであることに注意してくださいnoexcept。つまり、エラーをキャッチする方法がありません。

整数の引数が負の場合、surface_すでにホースが入っています。制約された値を使用する前に、制約チェックを行う方がよいでしょう。出来ますか?

4

3 に答える 3

17

名前付きコンストラクタ

いわゆる名前付きコンストラクター( https://isocpp.org/wiki/faq/ctors#named-ctor-idiomも参照) を使用して、コンストラクターを作成できprivateます。

class RenderTarget {
private:
    RenderTarget (int w, int h) :
        width_(w), height_(h), buffer_(w*h) 
    {
        // NOTE: Error checking completely removed.
    }

public:
    static RenderTarget create(int width, int height) {
        // Constraint Checking
        if (width<0 || height<0)
            throw std::logic_error("Crizzle id boom shackalack");

        return RenderTarget(width, height);
    }

名前付きコンストラクターは、温度 <-- 摂氏 |など、使用するのが曖昧な複数のコンストラクターがある場合に役立ちます。華氏 | ケルビンまたは距離 <-- メートル | ヤード | キュービット | キロメートル | ... .

そうでなければ、(個人的な意見ですが)予期せぬ抽象化と注意散漫をもたらすので、避けるべきです。

三項演算子とthrow

C++ では、[expr.cond]throwで、三項演算子 ( ?:-operator)の一方または両方のオペランドで -expressionsを使用できます。

RenderTarget(int w, int h) :
    width_(w<0 ? throw std::logic_error("Crizzle id boom shackalack") : w),
    height_(h<0 ? throw std::logic_error("Crizzle id boom shackalack") : h),
    surface_(w*h)
{}

引数を保存しない場合は?:、もちろん式の中で使用することもできます:

RenderTarget(int w, int h) :
    surface_(
       (w<0 ? throw std::logic_error("Crizzle id boom shackalack") : w)
     * (h<0 ? throw std::logic_error("Crizzle id boom shackalack") : h)
    )
{}

または、前提条件チェックを 1 つのオペランドに結合します。

RenderTarget(int w, int h) :
    surface_(
       (w<0||h<0) ? throw std::logic_error("Crizzle id boom shackalack") :
       w * h
    )
{}

-expression inline?:で -operator を使用すると、基本的な制約チェックに非常に便利であり、デフォルトのコンストラクター (存在する場合) を使用して、コンストラクター本体内で「実際の初期化」を行う必要がなくなります。throw

これは、より複雑なシナリオでは少し扱いに​​くくなる可能性があります。

静的プライベート メンバー

もちろん、両方の長所を活用できます。

private:
    static bool check_preconditions(int width, int height) {
        if (width<0 || height<0)
            return false;
        return true;
    }
public:
    RenderTarget(int w, int h) :
        surface_(
           check_preconditions(w,h) ?
           w*h :
           throw std::logic_error("Crizzle id boom shackalack")
        )
    {}

...または、事前条件チェックが必要なメンバーの静的関数を作成します。

private:
    static Buffer create_surface(int width, int height) {
        if (width<0 || height<0)
            throw std::logic_error("Crizzle id boom shackalack")
        return Buffer(width*height);
    }

public:
    RenderTarget(int w, int h) :
      surface_(create_surface(w, h))
    {}

これは、制約チェック用の完全な C++ 機構が手元にあり、たとえばロギングを簡単に追加できるため、便利です。これは適切にスケーリングされますが、単純なシナリオではあまり便利ではありません。

于 2014-05-23T13:56:20.337 に答える
3

インラインを使用したphresnel のソリューションと、符号なし整数を使用するというCurg の回答throwの提案はどちらも、ここでの一般的な解決策を示唆しています。

幅と高さを負にできない場合は、それらを符号なしにするのが適切な選択かもしれませんが、上限がある場合は、次のような不変条件を指定するためにより正確な型が必要になる場合があります。

template<class T, T min, T max>
struct ranged {
  ranged(const T v)
    : value_(v < min || v > max ? throw range_error("...") : v) {}
  const T value_;
};

次に、次のように言うかもしれません。

ranged<unsigned int, 0, 1600> width_;
ranged<unsigned int, 0, 1200> height_;

ただし、幅と高さの縦横比を 16:9 以下にする必要がある場合があります。Sizeしたがって、それらをタイプにまとめることができます。こうすることで、コンストラクター本体が開始するまでに、メンバーのすべての検証ロジックが完了しています。RenderTarget

この種のカプセル化は、オブジェクト指向プログラミングの基本です。オブジェクトのパブリック インターフェイスを使用してオブジェクトを無効な状態にすることはできず、コンストラクターはパブリック インターフェイスの一部です。

于 2014-05-25T07:28:20.907 に答える
2

サイズ、高さ、幅を符号なしにすることで問題を単純化する可能性もあります。これにより、負の状態になることを防ぐことができます。

class Buffer {
public:
    Buffer() = delete;
    Buffer(unsigned int size) noexcept;
};
....


class RenderTarget {
public:
    ....
private:
    unsigned int width_, height_;
    Buffer surface_;
};

コンストラクターは、整数引数の有効性をチェックする必要があります。

RenderTarget::RenderTarget(unsigned int width, unsigned int height) :
    width_(width), height_(height),
    surface_(width_*height)
{
    // never a need to throw on negative values...
}

追加のエラー処理アプローチ:

型を使用して無効な値を制限するだけでは不十分な場合は、次のように、例外をスローする以外のエラー状況を処理するための試行済みおよびテスト済みの方法が多数あります。

Buffer(int size, bool& success) {}

また

class Buffer {
...
    bool isValid()
};

また

template<typename T>
struct ValidatedValue
{
    ValidatedValue(T value, T min, T max)
        : _value(value)
        , _isValid(value >= min && value <= max)
    {
    }

    bool isValid() const { return _isValid; }
private:
    T _value;
    bool _isValid;
};

また ...

他の多くの選択肢。

データを検証するためのすべてのアプローチには長所と短所がありますが、多くの場合、これらのソリューションは過剰に設計されている可能性があるため、ソリューションを保守可能で読みやすいようにシンプルに保つことをお勧めします。

于 2014-05-23T14:09:28.713 に答える