C ++中的协变返回类型究竟是什么?

时间:2014-09-12 17:10:52

标签: c++ inheritance vector virtual covariant

尝试执行此操作时出现编译错误:

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*>应该是协变的好吧,但似乎并非如此。为什么呢?

7 个答案:

答案 0 :(得分:4)

协变返回类型允许在派生类中重写的虚拟成员函数返回不同类型的对象,只要它可以以与基类的返回类型相同的方式使用。计算机科学家(从那时起Barbara Liskov)就可以以相同的方式使用#34; 替代性的理论定义。

不,std::vector<B*>不是std::vector<A*>的子类型,也不是。{/ p>

例如,std::vector<B*>不支持push_back(A*)操作,因此它不可替代。

C ++根本不会尝试推断模板的子类型关系。只有在您实际专门化并指定基类时,才会存在该关系。其中一个原因,即使在理论上是协变的(基本上是只读的)接口上,C ++的版本实际上比Liskov替换更强 - 在C ++中,兼容性必须存在于二进制级别。由于相关对象集合的内存布局可能与子对象放置不匹配,因此未实现此二进制兼容性。协变返回类型仅限于指针或引用的限制也是二进制兼容性问题的结果。派生对象可能不适合为基本实例保留的空间......但它的指针会。

答案 1 :(得分:1)

模板不会&#34;继承&#34;协方差,因为不同的模板专业化可能完全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返回其中一些实际上是车辆的向量,您会有什么感受?它似乎打败了类型系统的重点吗?

答案 2 :(得分:1)

苹果是一种水果。

一袋苹果不是一袋水果。那是因为你可以把梨放在一袋水果里。

答案 3 :(得分:1)

C ++常见问题解答直接在[21.3] Is a parking-lot-of-Car a kind-of parking-lot-of-Vehicle? 中解答了这个问题(“你不必喜欢它。但你必须接受它。”)

所以问题Getting a vector into a function that expects a vector也在问同样的问题。答案是,虽然最初允许泛型类型的协方差似乎是安全的,特别是派生类型的容器被视为基类型的容器,但它是非常不安全的。

考虑以下代码:

class Vehicle {};
class Car : public Vehicle {};
class Boat : public Vehicle {};

void add_boat(vector<Vehicle*>& vehicles) { vehicles.push_back(new Boat()); }

int main()
{
  vector<Car*> cars;
  add_boat(cars);
  // Uh oh, if that worked we now have a Boat in our Cars vector.
  // Fortunately it is not legal to convert vector<Car*> as a vector<Vehicle*> in C++.
}

答案 4 :(得分:1)

该标准定义了§10.3[class.virtual] / p7中C ++目的的协方差:

  

覆盖函数的返回类型应与...相同   被覆盖函数的返回类型或协变与   功能的类。如果函数D::f覆盖函数   B::f,函数的返回类型是协变的   满足以下标准:

     
      
  • 都是指向类的指针,都是对类的左值引用,或者两者都是对类 113
  • 的rvalue引用   
  • 返回类型B::f中的类与返回类型D::f中的类相同,或者是明确的   返回中类的可访问直接或间接基类   类型D::f
  •   
  • 指针或引用都具有相同的cv资格,返回类型D::f中的类类型具有相同的cv资格   或者比返回类型中的类类型更少的cv资格   B::f
  •   
     

113 不允许使用指向类的多级指针或对类的多级指针的引用。

您的功能在第一点失败,即使您绕过它,第二点失败 - std::vector<A*>不是std::vector<B*>的基础。

答案 5 :(得分:0)

协方差仅在您返回指针或对类的引用时发生,并且类通过继承关联。

这显然没有发生,因为std::vector<?>不是指针也不是引用,并且因为两个std::vector<?>没有父/子关系。

现在,我们可以做到这一点。

第1步,创建一个array_view类。它有beginend指针和方法以及size方法,您可以期待所有这些。

步骤2,创建一个shared_array_view,这是一个数组视图,它也拥有一个带有自定义删除器的shared_ptr<void>:它在其他方面是相同的。该课程还确保所观看的数据持续时间足以观看。

步骤3,创建一个range_view,它是一对迭代器并穿上它。对具有所有权令牌的shared_range_view执行相同操作。将array_view修改为range_view,并提供一些额外的保证(主要是连续的迭代器)。

步骤4,编写转换迭代器。这是一个存储value_type_1上的迭代器的类型,它可以调用函数,也可以通过value_type_2隐式转换为const_iterator。

步骤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_());
  }
};

我描述的大部分内容都是通过提升完成的。

答案 6 :(得分:0)

这不起作用,因为

  1. 你没有返回指针或引用,这是协变返回工作所必需的;和
  2. 无论Foo<B>Foo<B>Foo如何,
  3. AB都没有继承关系(除非有专门化,否则就是这样)。
  4. 但我们可以解决这个问题。首先,请注意,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; }
    

    (*它) - &GT;操作符 - &GT;(); }             ITER&安培; operator ++(){++ * it;返回*这个; }             ITER&安培; operator - (){ - * it;返回*这个; }             iter operator ++(int){iter n(* this); ++ *它;返回n; }             iter operator - (int){iter n(* this); - *它;返回n; }             bool operator ==(const iter&amp; other)const {return * it == * other.it; }             bool operator!=(const iter&amp; other)const {return * it!= * other.it; }           私人的:             std :: unique_ptr 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创建一个模板化实现,查看不同类型元素的向量:

    模板 struct readonly_vector_view_impl:readonly_vector_view_base {     typedef typename readonly_vector_view_base :: 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; }
    

    it.operator-&GT;(); }         ITER&安培; operator ++(){++ it;返回*这个; }         ITER&安培; operator - (){++ it;返回*这个; }

        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;
    

    };

    好的,只要我们有两种类型,其中一种可以转换为另一种类型,例如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));
        }
    };
    

    一块蛋糕! Live demo完全值得付出努力!