0

数字の配列を含む Number クラスの場合を考えてみましょう。クラスのユーザーが数字に簡単にアクセスできるようにしたいので、[] 演算子をオーバーロードして、ユーザーが 10 の累乗で数字を選択できるようにします。number[2] は百を表す数字です。クラスはインデックス セーフです。ユーザーが範囲外のインデックスを指定すると、0 が返されます (10 = 0010 として)。

ただし、指数が桁を保持する内部テーブルのサイズを超える場合、後者を拡張する必要があるため、桁の設定にはもう少し手間がかかります。

ユーザーが数字を取得するかどうか、いつ保存するかなど、異なるアプローチを選択する必要があることは明らかです。私は次の解決策を思いつきました(クラスは部分的で、アイデアを示すためだけです):

class Number;

class Digit
{
    friend class Number;

private:
    Number number;
    int exponent;

    Digit(Number & newNumber, int newExponent) 
        : number(newNumber), exponent(newExponent)
    {
    }

public:
    operator unsigned char() 
    {
        return number.GetDigit(exponent);
    }

    void operator = (unsigned char digit) 
    {
        number.SetDigit(exponent, digit);
    }
};

class Number
{
    friend class Digit;

private:
    unsigned char GetDigit(int exponent) { ... };
    void SetDigit(int exponent, unsigned char value) { ... };

public:
    Digit operator [] (int exponent)
    {
        return Digit(*this, exponent);
    }
}

Digit クラス内の演算子は明らかに、ユーザーが値を割り当てて値を取得できるように、いくつかのプライベート Number のメソッドを呼び出します。また、ユーザーが値を取得するかどうか、およびいつ保存するかによって、異なる動作をすることもできます。

ただし、const には問題があります。Number クラスのインスタンスが const の場合、演算子 [] も const としてマークされていない限り呼び出すことはできません。ただし、数値 & が必要であり、const Number & が渡されるため、Digit クラスをインスタンス化することはできません。

1 つの解決策には const_cast の使用が含まれますが、ハックを使用しているように感じます。言うまでもなく、const を削除すると、ユーザーは Digit の演算子 = を使用して数値インスタンスを変更できるようになります。const_cast を使用したり、ConstDigit などの別のクラスを作成したりする以外に、この問題を別の方法で解決する方法はありますか?


もう 1 つの問題: Digit のインスタンスは、ユーザーの快適さのためにのみ提供されているため、ユーザーに保存してほしくありません。プライベート コピー コンストラクターは、ユーザーが次のような数字を格納できないようにします。

auto digit = number[5];

ただし、参照によって数字を保存することはできます。

auto digit & = number[5];

数値は参照によって数字に渡されますが (Digit コンストラクターを参照)、ポインターの問題を防ぎますが、この状況では AV を防ぐことはできません。

auto number = new Number();
auto & digit = (*number)[5];
delete number;
digit = 12;

ユーザーが Number で new 演算子を使用することを禁止するかもしれませんが、別の方法があるのでしょうか?


より長い答え、参照によって数字を返せない理由。

数値は 2 つの個別のベクトルとして保持されます。1 つは整数部分用で、もう 1 つは小数部分用です (問題の例を単純化しましたが、ここではあまり重要ではありません)。例えば、

123.456

として保存されます

intPart : {3, 2, 1}
fracPart : {4, 5, 6}

ユーザーが 100 桁目の数字を要求した場合、それが配列の範囲外であることを確認し、0 を返します (前述のように、10 = 0010 = 00010 = ... など)。

ただし、ユーザーが数字を設定したい場合は、配列を拡張して残りの指数をすべて埋める必要があります。

100 // user wants to set millions' digit to 5
5 [000] 100 // i have to add these empty digits

もちろん、[] 演算子でも同様に行うことができます。しかし、ユーザーが 100 桁目を 0 に設定することを決定した場合、98 桁を追加する必要はなく、確認する方法さえありません。先行ゼロと後続ゼロが存在します)。彼の割り当て後にストレージを圧縮する方法もありません。

もちろん、解決策の 1 つは、[] 演算子を削除して getter/setter を使用することです。しかし、全体のポイントは、クラスを使いやすくすることであり、[] 演算子は、getter/setter メソッドのペアよりもはるかにこの仮定を満たします:)

4

1 に答える 1

1

2番目の問題から答えを始めます。参照を取得する場合、アクセスしたいときにそれがまだ有効であることを確認する必要があります。あなたの例では、これは間違いなく当てはまりません。これは、以前に保存された参照を介してアクセスされる前に、参照先のオブジェクトがどこかで破棄された場合を示す単純な例である必要があることがわかります。この問題を (直接) 解決することはできません。

STLでもこの「問題」に遭遇する可能性があります。たとえば、ベクター要素への参照を取得し、ベクターに何かを挿入する場合は、新しいメモリ割り当てのためにコンテナーのコンテンツが別の場所に移動した可能性があるため、参照が (おそらく) 無効になることに注意してください。

これを回避する唯一の方法は、スマート ポインターを使用することです。少なくとも、オブジェクトへのアクセスが安全であることを保証します。これでは、新しいオブジェクトにアクセスできません。一部のコードで背面が変更され、新しい数字に置き換え(*number)[5]られた場合。

Number最初の質問に進みます。現在、オブジェクトに特定の数字を設定する方法がわかりません。数字の代入演算子がありNumber::operator[]ますが、値で数字を返すため、それを使用して数字を設定することはできません。

私の (誤解) 理解は、不完全なコード サンプルによるものだと思います。適当に伸ばしてください。Digitここでは、 a のは のコンストラクターNumberで初期化されNumber、 youroperator[]はアクセスのみを目的としていると仮定します。

const一般に、を返す上記の演算子のバージョンを作成できますconst Digit。オブジェクトが const でないことを 100% 確信できない場合はconst_cast、削除に使用しないでください。constそうしないと、未定義の動作が発生します。const_castただし、非 const オブジェクトへの追加/const非 const オブジェクトからの削除には安全に使用できます。constこれは、および非constアクセス メソッドのコードを再利用するのに役立ちます。非constメソッドは、 を追加constし、アクセス メソッドを呼び出してから、戻り値からconst安全に削除できます (まあ、これは実装によって異なります)。const詳細については、Scott Meyers の「Effective C++」を参照してください。

とは言っても、私は通常、 を呼び出すときにオブジェクトへの参照を期待していることを追加する必要がありますoperator[]。これにより、オブジェクトを変更できる場合があります (例:std::vectorまたはstd::map)。読み取り専用アクセスのみを目的としている場合は、(非 const) 値ではなく、const 参照または const 値によって返されます。そうでなければ、次のような間違い

Number[5] = 12;

起こり、気づかれなくなります。12読者は、 が Digit に割り当てられていると想定しstored in Number[5]ます。実際、それは前述のコピーにのみDigit保存されます。Number::operator[]のみが返される場合const Digit、コンパイラはこれをキャッチします。

編集

値で返す方法はわかりましたが、元の番号は変更されています。ただし、このインターフェイスはまだ直感的ではありません。ほとんどの場合、クラスに演算子を提供することは、それが一貫した、またはより直感的な動作を提供する場合にのみ意味があります。あなたのoperator[]宣言を見ると、それがDigit値によって返されていることがわかります。あなたのインターフェースは私に言う、私はコピーを取得します。内部でまだNumberオブジェクトを変更していることを確認する方法はありません。

したがって、getter/setter メソッドを使用すると、インターフェースがより明確になると思います。

于 2012-04-17T07:25:18.743 に答える