代码之家  ›  专栏  ›  技术社区  ›  Peter Smit

为什么不可能将const set<derived*>作为const set<base*>传递给函数?

  •  2
  • Peter Smit  · 技术社区  · 16 年前

    在这被标记为重复之前,我知道 this 问题,但在我的例子中,我们讨论的是常量容器。

    我有两个班:

    class Base { };
    class Derived : public Base { };
    

    以及一个函数:

    void register_objects(const std::set<Base*> &objects) {}
    

    我想调用此函数作为:

    std::set<Derived*> objs;
    register_objects(objs);
    

    编译器不接受这个。为什么不?该集不可修改,因此不存在将非派生对象插入其中的风险。我怎样才能做到最好?

    编辑:
    我知道现在编译器的工作方式是 set<Base*> set<Derived*> 完全无关,因此找不到函数签名。然而,我现在的问题是:为什么编译器是这样工作的?有什么反对意见吗 const set<Derived*> 作为 const set<Base*>

    9 回复  |  直到 16 年前
        1
  •  13
  •   YeahStu    16 年前

    编译器不接受这一点的原因是标准告诉它不要这样做。

    标准之所以不这样做,是因为委员会没有引入一条规则 const MyTemplate<Derived*> 是相关类型 const MyTemplate<Base*> 即使非常量类型不相关。他们当然不想为std::set设置一个特殊的规则,因为一般来说,语言不会为库类设置特殊的情况。

    标准委员会不想使这些类型相关的原因是,MyTemplate可能没有容器的语义。考虑:

    template <typename T>
    struct MyTemplate {
        T *ptr;
    };
    
    template<>
    struct MyTemplate<Derived*> {
        int a;
        void foo();
    };
    
    template<>
    struct MyTemplate<Base*> {
        std::set<double> b;
        void bar();
    };
    

    那么通过一个 const mytemplate<派生*> 作为一个 const mytemplate<基础*> ?这两个类没有共同的成员函数,并且布局不兼容。您需要在这两者之间使用转换运算符,否则编译器将不知道要做什么,不管它们是常量还是常量。但是,在标准中定义模板的方式,即使没有模板专门化,编译器也不知道该做什么。

    std::set 它本身可以提供一个转换操作符,但这只需要制作一个副本(*),您可以很容易地做到这一点。如果有这样的事情 std::immutable_set ,那么我认为有可能实现 std::immutable_set<Base*> 可以用 std::immutable_set<Derived*> 只是指向同一个皮条客。即使如此,如果在派生类中重载了非虚拟运算符,也会发生奇怪的事情——基容器将调用基版本,因此,如果集合具有非默认的比较器(它对对象本身而不是其地址执行任何操作),转换可能会对其取消排序。因此,这种转变会带来严重的警告。但无论如何,没有 immutable_set 和const不是不变的。

    另外,假设 Derived 与…有关 Base 通过虚拟继承或多重继承。那你就不能重新解释 衍生的 地址 基地 :在大多数实现中,隐式转换会更改地址。因此,不能成批转换包含 Derived* 作为包含 Base* 不复制结构。但是C++标准实际上允许这种情况发生在任何非POD类中,而不仅仅是多重继承。和 衍生的 是非pod,因为它有一个基类。所以为了支持这一变化 STD::设置 ,继承和结构布局的基础必须更改。这是C++语言的一个基本限制,标准容器不能按您想要的方式重新解释,并且我不知道可以在不降低效率或便携性的情况下实现它们的任何技巧。这很令人沮丧,但这很难。

    因为您的代码正在传递一个按值设置的值,所以您只需复制:

    std::set<Derived*> objs;
    register_objects(std::set<Base*>(objs.begin(), objs.end());
    

    [编辑:您已将代码示例更改为不传递值。我的代码仍然有效,除了重构调用代码以使用 std::set<Base*> 首先。]

    正在为编写包装 std::设置<基础*> 确保所有元素 导出* Java泛型的工作方式比安排要高效的转换更容易。所以你可以这样做:

    template<typename T, typename U>
    struct MySetWrapper {
        // Requirement: std::less is consistent. The default probably is, 
        // but for all we know there are specializations which aren't. 
        // User beware.
        std::set<T> content;
        void insert(U value) { content.insert(value); }
        // might need a lot more methods, and for the above to return the right
        // type, depending how else objs is used.
    };
    
    MySetWrapper<Base*,Derived*> objs;
    // insert lots of values
    register_objects(objs.content);
    

    (*)实际上,我想它可以在写时复制,在以典型方式使用常量参数的情况下,这意味着它不需要进行复制。但是,在STL的实现中,copy-on-write有点不可信,即使我不怀疑委员会是否会要求如此重要的实现细节。

        2
  •  2
  •   xtofl Adam Rosenfield    16 年前

    如果你 register_objects 函数接收一个参数,它可以放入/期望 任何 Base 其中的子类。这就是它的签名。

    这违反了里斯科夫替代原理。

    这个特殊的问题也被称为 Covariance . 在这种情况下,如果函数参数是常量容器,那么 能够 被强迫去工作。如果参数容器是可变的,它就不能工作。

        3
  •  2
  •   Naveen    16 年前

    先看看这里: Is array of derived same as array of base .在您的情况下,派生集与基集是完全不同的容器,并且由于没有隐式转换运算符可用于在它们之间转换,所以编译器给出了一个错误。

        4
  •  2
  •   Daniel Daranas    16 年前

    std::set<Base*> std::set<Derived*> 基本上是两个不同的物体。尽管基类和派生类是通过继承链接的,但在编译器模板实例化级别,它们是两种不同的实例化(集合)。

        5
  •  1
  •   Goz    16 年前

    首先,似乎有点奇怪,你没有通过参考…

    其次,正如另一篇文章中提到的,最好将传入集创建为std::set<base*>,然后为每个set成员在中新建派生类。

    你的问题当然是因为这两种类型完全不同。就编译器而言,std::set<derived*>绝不从std::set<base*>继承。它们只是两种不同类型的集合…

        6
  •  0
  •   neuro    16 年前

    好吧,正如你提到的问题所述, set<Base*> set<Derived*> 是不同的对象。您的register_objects()函数接受 设置<base*> 对象。因此编译器不知道任何寄存器\u objects()接受 设置<派生的*> . 参数的常量不会改变任何东西。在引用的问题中陈述的解决方案似乎是你能做的最好的事情。取决于你需要做什么…

        7
  •  0
  •   MSalters    16 年前

    如您所知,一旦删除非常量操作,这两个类就非常相似。但是,C++中继承是类型的一个属性,而const仅仅是类型的限定符。这意味着你不能正确地陈述 const X 来源于 const Y ,即使x从y派生。

    此外,如果x不从y继承,这也适用于x和y的所有cv限定变体。这延伸到 std::set 实例化。自从 std::set<Foo> 不从继承 std::set<bar> , std::set<Foo> const 不从继承 std::set<bar> const 要么。

        8
  •  0
  •   Daniel Earwicker    16 年前

    您完全正确地认为这是逻辑上允许的,但是它需要进一步的语言特性。如果您有兴趣了解其他语言的方式,可以在C 4.0中找到它们。请参见这里: http://community.bartdesmet.net/blogs/bart/archive/2009/04/13/c-4-0-feature-focus-part-4-generic-co-and-contra-variance-for-delegate-and-interface-types.aspx

        9
  •  0
  •   RAC    16 年前

    没有看到它链接,所以这里有一个弹着点在C++ FAQ Lite相关的:

    http://www.parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.3

    我想他们的苹果袋!一袋水果的类比适合这个问题。