2

これを実行しようとすると、コンパイル エラーが発生します。

class A
{
    virtual std::vector<A*> test() { /* do something */ };
}

class B: public A
{
    virtual std::vector<B*> test() { /* do something */ };
}

A と B は共変型であると想定しているため、A* と B* も (正しい?) である必要があります。推論により、私はそれも共変であるstd::vector<A*>と予想しstd::vector<B*>ていましたが、そうではないようです。なんで?

4

7 に答える 7

1

テンプレートは共分散を「継承」しません。これは、異なるテンプレートの特殊化が完全に 100% 無関係である可能性があるためです。

template<class T> struct MD;

//pets
template<> struct MD<A*> 
{
    std::string pet_name;
    int pet_height;
    int pet_weight;
    std::string pet_owner;
};

//vehicles
template<> struct MD<B*>
{
    virtual ~MD() {}
    virtual void fix_motor();
    virtual void drive();
    virtual bool is_in_the_shop()const;
}

std::vector<MD<A*>> get_pets();

get_petsそれらの一部が実際に乗り物であるベクトルが返されたらどう思いますか? 型システムのポイントを打ち負かすようですよね?

于 2014-09-12T17:17:03.947 に答える
0

共分散は、ポインターまたはクラスへの参照を返す場合にのみ発生し、クラスは継承によって関連付けられています。

std::vector<?>がポインタでも参照でもないため、また 2 つstd::vector<?>の s に親子関係がないため、これは明らかに発生していません。

これで、これを機能させることができます。

ステップ 1、array_viewクラスを作成します。これには、beginポインターendとメソッドとメソッドと、size期待されるすべてのものがあります。

ステップ 2、 を作成します。これは、カスタム デリータを備えたshared_array_viewも所有する配列ビューです。それ以外は同じです。shared_ptr<void>このクラスは、表示しているデータが表示されるのに十分な長さであることも保証します。

ステップ 3、range_viewイテレータとドレッシングのペアである を作成します。shared_range_view所有権トークンを使用して同じことを行います。いくつかの追加の保証 (主に連続したイテレータ) を持つarray_viewように変更します。range_view

ステップ 4、変換イテレータを記述します。これはvalue_type_1、関数を呼び出すか、暗黙的に const_iterator over に変換する反復子を格納する型ですvalue_type_2

ステップ 5、いつを暗黙的に に変換できるかrange_view< implicit_converting_iterator< T*, U* > >を返す関数を作成します。T*U*

ステップ6、上記の書き込み型消しゴム

class A {
  owning_array_view<A*> test_() { /* do something */ }
  virtual type_erased_range_view<A*> test() { return test_(); };
};

class B: public A {
  owning_array_view<B*> test_() { /* do something */ };
  virtual type_erased_range_view<A*> test() override {
    return convert_range_to<A*>(test_());
  }
};

私が説明することのほとんどは、ブーストによって行われました。

于 2014-09-12T17:55:06.090 に答える
0

これは機能しません。

  1. 共変リターンが機能するために必要なポインターまたは参照を返していません。と
  2. Foo<B>andに関係なく、Foo<B>継承関係はありません(そうする特殊化がない限り)。FooAB

しかし、私たちはそれを回避することができます。まず、要素の追加をサポートできないという理由だけで、言語の制限に関係なく、std::vector<A*>std::vector<B*>は互いに置換できないことに注意してください。そのため、代わりになるカスタムアダプターを作成することさえできませんstd::vector<B*>A*std::vector<B*>std::vector<A*>

しかし、 の読み取り専用コンテナーは、 の読み取り専用コンテナーのB*ように見えるように適合させることができますA*。これは多段階のプロセスです。

読み取り専用のコンテナーのようなインターフェースをエクスポートする抽象クラス テンプレートを作成する

template <class ApparentElemType>
struct readonly_vector_view_base
{
    struct iter
    {
        virtual std::unique_ptr<iter> clone() const = 0;

        virtual ApparentElemType operator*() const = 0;
        virtual iter& operator++() = 0;
        virtual iter& operator--() = 0;
        virtual bool operator== (const iter& other) const = 0;
        virtual bool operator!= (const iter& other) const = 0;
        virtual ~iter(){}
    };

    virtual std::unique_ptr<iter> begin() = 0;
    virtual std::unique_ptr<iter> end() = 0;

    virtual ~readonly_vector_view_base() {}
};

イテレータ自体ではなく、イテレータへのポインタを返しますが、心配する必要はありません。このクラスは、とにかく STL のようなラッパーによってのみ使用されます。

次に、 とそのイテレータの具体的なラッパーを作成してreadonly_vector_view_base、 へのポインタを含め、その操作を に委譲しますreadonly_vector_view_base

template <class ApparentElemType>
class readonly_vector_view
{
  public:
    readonly_vector_view(const readonly_vector_view& other) : pimpl(other.pimpl) {}
    readonly_vector_view(std::shared_ptr<readonly_vector_view_base<ApparentElemType>> pimpl_) : pimpl(pimpl_) {}

    typedef typename readonly_vector_view_base<ApparentElemType>::iter iter_base;
    class iter
    {
      public:
        iter(std::unique_ptr<iter_base> it_) : it(it_->clone()) {}
        iter(const iter& other) : it(other.it->clone()) {}
        iter& operator=(iter& other) { it = other.it->clone(); return *this; }

        ApparentElemType operator*() const { return **it; }

        iter& operator++() { ++*it; return *this; }
        iter& operator--() { --*it; return *this; }
        iter operator++(int) { iter n(*this); ++*it; return n; }
        iter operator--(int) { iter n(*this); --*it; return n; }

        bool operator== (const iter& other) const { return *it == *other.it; }
        bool operator!= (const iter& other) const { return *it != *other.it; }
      private:
        std::unique_ptr<iter_base> it;
    };

    iter begin() { return iter(pimpl->begin()); }
    iter end() { return iter(pimpl->end()); }
  private:
    std::shared_ptr<readonly_vector_view_base<ApparentElemType>> pimpl;
};

readonly_vector_view_base次に、異なる型の要素のベクトルを参照するテンプレート化された実装を作成します。

template <class ElemType, class ApparentElemType>
struct readonly_vector_view_impl : readonly_vector_view_base<ApparentElemType>
{
    typedef typename readonly_vector_view_base<ApparentElemType>::iter iter_base;

    readonly_vector_view_impl(std::shared_ptr<std::vector<ElemType>> vec_) : vec(vec_) {}

    struct iter : iter_base
    {
        std::unique_ptr<iter_base> clone() const { std::unique_ptr<iter_base> x(new iter(it)); return x; }

        iter(typename std::vector<ElemType>::iterator it_) : it(it_) {}

        ApparentElemType operator*() const { return *it; }

        iter& operator++() { ++it; return *this; }
        iter& operator--() { ++it; return *this; }

        bool operator== (const iter_base& other) const {
            const iter* real_other = dynamic_cast<const iter*>(&other);
            return (real_other && it == real_other->it);
        }
        bool operator!= (const iter_base& other) const { return ! (*this == other); }

        typename std::vector<ElemType>::iterator it;
    };

    std::unique_ptr<iter_base> begin() {
        iter* x (new iter(vec->begin()));
        std::unique_ptr<iter_base> y(x);
        return y;
    }
    std::unique_ptr<iter_base> end() {
        iter* x (new iter(vec->end()));;
        std::unique_ptr<iter_base> y(x);
        return y;
    }

    std::shared_ptr<std::vector<ElemType>> vec;
};

OK、 と のように一方が他方に変換可能な 2 つの型がある限り、 のベクトルを のベクトルであるかのようにA*見るB*ことができます。B*A*

しかし、それは私たちに何をもたらしますか? はまだ!readonly_vector_view<A*>とは無関係です。readonly_vector_view<B*>読む...

共変の戻り値の型は実際には必要ないことがわかりました。それ以外の場合は、C++ で使用できるものに対する構文糖衣です。C++ に共変の戻り値の型がない場合、それらをシミュレートできますか? それは実際にはとても簡単です:

class Base
{
   virtual Base* clone_Base() { ... actual impl ... }
   Base* clone() { return clone_Base(); } // note not virtual 
};

class Derived : public Base
{
   virtual Derived* clone_Derived() { ... actual impl ... }
   virtual Base* clone_Base() { return clone_Derived(); }
   Derived* clone() { return clone_Derived(); } // note not virtual 

};

実際には非常に簡単で、戻り値の型がポインターまたは参照である必要も、継承関係を持つ必要もありません。変換があれば十分です。

class Base
{
   virtual shared_ptr<Base> clone_Base() { ... actual impl ... }
   shared_ptr<Base> clone() { return clone_Base(); } 
};

class Derived : public Base
{
   virtual shared_ptr<Derived> clone_Derived() { ... actual impl ... }
   virtual shared_ptr<Base> clone_Base() { return clone_Derived(); }
   shared_ptr<Derived> clone() { return clone_Derived(); } 
};

同様に、 を返し、A::test()を返すように手配できます。これらの関数は仮想ではないため、戻り値の型が何らかの関係にある必要はありません。一方が他方を隠しているだけです。しかし、内部では、実装された を作成する (たとえば) を作成する仮想関数を呼び出します。これは、 の観点から実装されており、すべてが実際の共変の戻り値の型であるかのように機能します。readonly_vector_view<A*>B::test()readonly_vector_view<B*>readonly_vector_view<A*>readonly_vector_view_impl<B*, A*>vector<B*>

struct A
{
    readonly_vector_view<A*> test() { return test_A(); }
    virtual readonly_vector_view<A*> test_A() = 0;
};

struct B : A
{
    std::shared_ptr<std::vector<B*>> bvec;

    readonly_vector_view<B*> test() { return test_B(); }

    virtual readonly_vector_view<A*> test_A() {
        return readonly_vector_view<A*>(std::make_shared<readonly_vector_view_impl<B*, A*>>(bvec));
    }
    virtual readonly_vector_view<B*> test_B() {
        return readonly_vector_view<B*>(std::make_shared<readonly_vector_view_impl<B*, B*>>(bvec));
    }
};

簡単です!ライブデモ努力する価値は十分にあります!

于 2014-09-12T19:07:23.660 に答える