【问题标题】:Multiple inheritance dilemma in C++C++中的多重继承困境
【发布时间】:2009-10-04 13:23:32
【问题描述】:

我在设计我的 C++ 库时遇到了问题。它是一个用于读取流的库,支持我在其他“流”实现中没有找到的功能。为什么我决定开始写它并不重要。关键是我有一个流类,它通过多重继承提供两个重要的行为:可共享性和可搜索性。

可共享流是那些具有 shareBlock(size_t length) 方法的流,该方法返回与其父流共享资源的新流(例如,使用父流使用的相同内存块)。可搜索的流是那些......好吧,可搜索的。通过方法 seek(),这些类可以搜索到流中的给定点。并非库的所有流都是可共享和/或可搜索的。

同时提供资源查找和共享实现的流类继承了称为 Seekable 和 Shareable 的接口类。如果我知道这样一个流的类型,那就太好了,但是,有时,我可能希望一个函数接受一个流作为参数,该流仅满足同时可搜索和可共享的质量,而不管它实际上是哪个流类是。我可以这样做创建另一个继承 Seekable 和 Shareable 的类并引用该类型,但是我必须使我的类既可搜索又可共享从该类继承。如果要添加更多像这样的“行为类”,我需要在代码的各处进行一些修改,很快就会导致代码无法维护。有没有办法解决这个困境?如果不是,那么我绝对会理解为什么人们对多重继承不满意。它几乎完成了这项工作,但是,就在那时,它没有:D

感谢任何帮助。

-- 第二次编辑,首选问题解决方案--

起初我认为Managu's 解决方案将是我的首选。然而,Matthieu M. 带来了另一个我比 Managu 更喜欢的:使用boost::enable_if<>。如果BOOST_MPL_ASSERT 生成的消息不是那么令人毛骨悚然,我想使用 Managu 的解决方案。如果有任何方法可以创建有启发性的编译时错误消息,我肯定会这样做。但是,正如我所说,可用的方法会产生令人毛骨悚然的信息。所以我更喜欢在不满足boost::enable_if<> 条件时产生的(更)指导性较低但更清晰的消息。

我已经创建了一些宏来简化编写模板函数的任务,这些模板函数接受继承选择类类型的参数,在这里:

// SonettoEnableIfDerivedMacros.h
#ifndef SONETTO_ENABLEIFDERIVEDMACROS_H
#define SONETTO_ENABLEIFDERIVEDMACROS_H

#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/array/elem.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/and.hpp>
#include <boost/type_traits/is_base_and_derived.hpp>
#include <boost/utility/enable_if.hpp>

/*
    For each (TemplateArgument,DerivedClassType) preprocessor tuple,
    expand: `boost::is_base_and_derived<DerivedClassType,TemplateArgument>,'
*/
#define SONETTO_ENABLE_IF_DERIVED_EXPAND_CONDITION(z,n,data) \
        boost::is_base_and_derived<BOOST_PP_TUPLE_ELEM(2,1,BOOST_PP_ARRAY_ELEM(n,data)), \
                BOOST_PP_TUPLE_ELEM(2,0,BOOST_PP_ARRAY_ELEM(n,data))>,

/*
    ReturnType: Return type of the function
    DerivationsArray: Boost.Preprocessor array containing tuples in the form
            (TemplateArgument,DerivedClassType) (see
                    SONETTO_ENABLE_IF_DERIVED_EXPAND_CONDITION)

    Expands:
    typename boost::enable_if<
            boost::mpl::and_<
                    boost::is_base_and_derived<DerivedClassType,TemplateArgument>,
                    ...
                    boost::mpl::bool_<true> // Used to nullify trailing comma
            >, ReturnType>::type
*/
#define SONETTO_ENABLE_IF_DERIVED(ReturnType,DerivationsArray) \
        typename boost::enable_if< \
                boost::mpl::and_< \
                        BOOST_PP_REPEAT(BOOST_PP_ARRAY_SIZE(DerivationsArray), \
                            SONETTO_ENABLE_IF_DERIVED_EXPAND_CONDITION,DerivationsArray) \
                        boost::mpl::bool_<true> \
            >, ReturnType>::type

#endif

// main.cpp: Usage example
#include <iostream>
#include "SonettoEnableIfDerivedMacros.h"

class BehaviourA
{
public:
    void behaveLikeA() const { std::cout << "behaveLikeA()\n"; }
};

class BehaviourB
{
public:
    void behaveLikeB() const { std::cout << "behaveLikeB()\n"; }
};

class BehaviourC
{
public:
    void behaveLikeC() const { std::cout << "behaveLikeC()\n"; }
};

class CompoundBehaviourAB : public BehaviourA, public BehaviourB {};
class CompoundBehaviourAC : public BehaviourA, public BehaviourC {};
class SingleBehaviourA : public BehaviourA {};

template <class MustBeAB>
SONETTO_ENABLE_IF_DERIVED(void,(2,((MustBeAB,BehaviourA),(MustBeAB,BehaviourB))))
myFunction(MustBeAB &ab)
{
    ab.behaveLikeA();
    ab.behaveLikeB();
}

int main()
{
    CompoundBehaviourAB ab;
    CompoundBehaviourAC ac;
    SingleBehaviourA    a;

    myFunction(ab); // Ok, prints `behaveLikeA()' and `behaveLikeB()'
    myFunction(ac); // Fails with `error: no matching function for
                    // call to `myFunction(CompoundBehaviourAC&)''
    myFunction(a);  // Fails with `error: no matching function for
                    // call to `myFunction(SingleBehaviourA&)''
}

如您所见,错误消息异常干净(至少在 GCC 3.4.5 中)。但它们可能具有误导性。它不会通知您您传递了错误的参数类型。它通知您函数不存在(事实上,它不是由于 SFINAE;但用户可能并不完全清楚)。不过,比起randomStuff ... ************** garbage ************** BOOST_MPL_ASSERT 产生的那些,我更喜欢那些干净的消息。

如果您在此代码中发现任何错误,请对其进行编辑和更正,或就此发表评论。我在这些宏中发现的一个主要问题是它们仅限于某些 Boost.Preprocessor 限制。在这里,例如,我只能将最多 4 个项目的DerivationsArray 传递给SONETTO_ENABLE_IF_DERIVED()。我认为这些限制是可配置的,也许它们甚至会在即将到来的 C++1x 标准中被取消,不是吗?如果我错了,请纠正我。我不记得他们是否建议对预处理器进行更改。

谢谢。

【问题讨论】:

    标签: c++ multiple-inheritance


    【解决方案1】:

    只是一些想法:

    STL 在迭代器和函子方面也存在同样的问题。那里的解决方案基本上是从等式中删除类型,记录需求(作为“概念”),并使用相当于鸭子类型的东西。这非常符合编译时多态性的策略。

    也许中间立场是创建一个模板函数,在实例化时静态检查其条件。这是一个草图(我不保证会编译)。

    class shareable {...};
    class seekable {...};
    
    template <typename StreamType>
    void needs_sharable_and_seekable(const StreamType& stream)
    {
        BOOST_STATIC_ASSERT(boost::is_base_and_derived<shareable, StreamType>::value);
        BOOST_STATIC_ASSERT(boost::is_base_and_derived<seekable, StreamType>::value);
        ....
    }
    

    编辑:花了几分钟确认编译,并“清理”错误消息:

    #include <boost/type_traits/is_base_and_derived.hpp>
    #include <boost/mpl/assert.hpp>
    
    class shareable {};
    class seekable {};
    
    class both : public shareable, public seekable
    {
    };
    
    
    template <typename StreamType>
    void dosomething(const StreamType& dummy)
    {
      BOOST_MPL_ASSERT_MSG((boost::is_base_and_derived<shareable, StreamType>::value),
                           dosomething_requires_shareable_stream, 
                           (StreamType));
      BOOST_MPL_ASSERT_MSG((boost::is_base_and_derived<seekable, StreamType>::value),
                           dosomething_requires_seekable_stream, 
                           (StreamType));
    }
    
    int main()
    {
      both b;
      shareable s1;
      seekable s2;
      dosomething(b);
      dosomething(s1);
      dosomething(s2);
    }
    

    【讨论】:

    • 这似乎是解决问题的好方法。我会玩弄我的一些想法。完成后我可能会接受你的回答,我会用我找到的解决方案编辑我的问题。谢谢。
    • +1 用于使用断言的“msg”变体,使编译错误更具可读性!
    【解决方案2】:

    看看boost::enable_if

    // Before
    template <class Stream>
    some_type some_function(const Stream& c);
    
    // After
    template <class Stream>
    boost::enable_if<
      boost::mpl::and_<
        boost::is_base_and_derived<Shareable,Stream>,
        boost::is_base_and_derived<Seekable,Stream>
      >,
      some_type
    >
    some_function(const Stream& c);
    

    感谢SFINAE,仅当 Stream 满足要求时才会考虑此功能,即这里派生自 Shareable 和 Seekable。

    【讨论】:

      【解决方案3】:

      使用模板方法怎么样?

      template <typename STREAM>
      void doSomething(STREAM &stream)
      {
        stream.share();
        stream.seek(...);
      }
      

      【讨论】:

      • 简单地使用模板会使事情变得非常不直观。使用了想要的接口,但是新手库用户不会注意到该接口属于哪个类。按照建议使用boost::enable_ifBOOST_MPL_ASSERT 效果会更好。
      • 但是您仍然必须使用 dynamic_cast<..> 向下转换流,因为您的参数仍然是流。模板解决方案更快,因为“转换”是在编译时完成的。当然不是那么直观。
      • 不。 enable_if 解决方案不需要强制转换,因为它也使用模板,就像您的一样,但添加了额外的每种类型检查。所需的类型仍然没有出现在错误消息中,但人们至少可以在函数签名中找到它们。阅读我编辑的帖子并测试示例代码以获取更多详细信息,我认为这可能是值得的。
      • @n2liquid:你是对的。在考虑了我写的内容后,我得出了同样的结论:我只是愚蠢到海外它使用模板......对此感到抱歉
      【解决方案4】:

      您可能想要Decorator pattern

      【讨论】:

        【解决方案5】:

        假设SeekableShareable 有共同的祖先,我能想到的一种方法是尝试向下转换(当然,asserts 替换为您的错误检查):

        void foo(Stream *s) {
            assert(s != NULL);
            assert(dynamic_cast<Seekable*>(s) != NULL);
            assert(dynamic_cast<Shareable*>(s) != NULL);
        }
        

        【讨论】:

          【解决方案6】:

          将 'shareable' 和 'seekable' 替换为 'in' 和 'out' 并找到您的 'io' 解决方案。在图书馆中,类似的问题应该有类似的解决方案。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-03-29
            • 2017-03-31
            • 2012-05-14
            • 2013-08-28
            • 2011-01-16
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多