11

一部の変数への読み取り/書き込みアクセスに注意することを意味する、ソフトウェアの優れた設計を定義しようとしています。ここでは、議論のためにプログラムを単純化しました。うまくいけば、これは他の人にも役立つでしょう。:-)

次のようなクラス X があるとします。

class X {
    int x;
public:
    X(int y) : x(y) { }
    void print() const { std::cout << "X::" << x << std::endl; }
    void foo() { ++x; }
};

print()また、将来、このクラスは X1、X2、... でサブクラス化され、 andを再実装できるとしましょうfoo()virtual(これは私が直面している実際の問題ではないため、簡単にするためにここでは必要なキーワードを省略しました。)

ポリモーフィズムを使用するため、(スマート) ポインターを使用して単純なファクトリを定義しましょう。

using XPtr = std::shared_ptr<X>;
using ConstXPtr = std::shared_ptr<X const>;

XPtr createX(int x) { return std::make_shared<X>(x); }

これまでのところ、すべて問題ありません。どちらgoo(p)が読み取りと書き込みが可能でphoo(p)どちらが読み取りのみ可能かを定義できますp

void goo(XPtr p) {
    p->print();
    p->foo();
    p->print();
}

void hoo(ConstXPtr p) {
    p->print();
//    p->foo(); // ERROR :-)
}

呼び出しサイトは次のようになります。

    XPtr p = createX(42);

    goo(p);
    hoo(p);

X( XPtr) への共有ポインターは、その const バージョン ( ConstXPtr)に自動的に変換されます。いいですね、まさに私が欲しいものです!

ここで問題が発生します: の異種コレクションが必要ですX。私の選択はstd::vector<XPtr>. (それは である可能性もありますlist。なぜでしょうか。)

私が考えているデザインは以下です。コンテナーには 2 つのバージョンがあります。1 つは要素への読み取り/書き込みアクセス、もう 1 つは要素への読み取り専用アクセスです。

using XsPtr = std::vector<XPtr>;
using ConstXsPtr = std::vector<ConstXPtr>;

このデータを処理するクラスがあります。

class E {
    XsPtr xs;
public:
    E() {
        for (auto i : { 2, 3, 5, 7, 11, 13 }) {
            xs.emplace_back(createX(std::move(i)));
        }
    }

    void loo() {
        std::cout << "\n\nloo()" << std::endl;
        ioo(toConst(xs));

        joo(xs);

        ioo(toConst(xs));
    }

    void moo() const {
        std::cout << "\n\nmoo()" << std::endl;
        ioo(toConst(xs));

        joo(xs); // Should not be allowed

        ioo(toConst(xs));
    }
};

ioo()および関数は次のjoo()とおりです。

void ioo(ConstXsPtr xs) {
    for (auto p : xs) {
        p->print();
//        p->foo(); // ERROR :-)
    }
}

void joo(XsPtr xs) {
    for (auto p: xs) {
        p->foo();
    }
}

ご覧のとおり、 inE::loo()E::moo()次のように変換する必要がありますtoConst()

ConstXsPtr toConst(XsPtr xs) {
    ConstXsPtr cxs(xs.size());
    std::copy(std::begin(xs), std::end(xs), std::begin(cxs));
    return cxs;
}

しかし、それはすべてを何度もコピーすることを意味します.... :-/

また、moo()const である では、のデータjoo()を変更するを呼び出すことができます。xs私が欲しかったものではありません。ここでは、コンパイル エラーを優先します。

完全なコードはideone.comで入手できます。

問題は、ベクトルを const バージョンにコピーせずに同じことを行うことは可能ですか? または、より一般的に、効率的で理解しやすい優れたテクニック/パターンはありますか?

ありがとうございました。:-)

4

4 に答える 4

6

通常の答えは、クラステンプレートX<T>の場合、すべてを特殊化できるため、コンパイラーはポインターまたは参照をX<const T>変換できると単純に想定することはできず、これら2つが実際に変換可能であることを表現する一般的な方法はないということだと思います. しかし、それから私は: 待って、IS Aと言う方法があります。IS Aは継承によって表現されます。X<T>X<const T>X<T> X<const T>

これはコンテナや標準のコンテナには役に立ちませんstd::shared_ptrが、独自のクラスを実装するときに使用するテクニックです。実際、std::shared_ptrこれをサポートするためにコンテナを改善できるかどうか、また改善する必要があるかどうか疑問に思っています。誰でもこれに問題がありますか?

私が念頭に置いているテクニックは、次のように機能します。

template< typename T > struct my_ptr : my_ptr< const T >
{
    using my_ptr< const T >::my_ptr;
    T& operator*() const { return *this->p_; }
};

template< typename T > struct my_ptr< const T >
{
protected:
    T* p_;

public:
    explicit my_ptr( T* p )
      : p_(p)
    {
    }

    // just to test nothing is copied
    my_ptr( const my_ptr& p ) = delete;

    ~my_ptr()
    {
        delete p_;
    }

    const T& operator*() const { return *p_; }
};

実際の例

于 2013-10-27T14:56:19.967 に答える
1

あなたがやりたいことには根本的な問題があります。

Astd::vector<T const*>は a の制限ではなく、同じことがスマート ポインターとそのバージョンを含む sstd::vector<T*>にも当てはまります。vectorconst

具体的には、ポインタをconst int foo = 7;最初のコンテナに格納できますが、2 番目のコンテナには格納できません。 std::vector範囲とコンテナの両方です。T**T const**問題に似ています。

現在、技術的std::vector<T const*> constには の制限ですがstd::vector<T>、これはサポートされていません。

これを回避する方法は、範囲ビューのいずれかで作業を開始することです。他のコンテナーへの非所有ビューです。への非所有T const*イテレータ ビューstd::vector<T *>が可能であり、必要なインターフェイスを提供できます。

boost::rangeボイラープレートを作成できますが、自分でcontiguous_range_view<T>orを書くのrandom_range_view<RandomAccessIterator>は難しくありません。イテレータ カテゴリを自動検出し、それに基づいて機能を有効にしたい場合は、boost::rangeより多くのコードが含まれます。

于 2013-10-28T01:25:57.883 に答える