C++ 中的多重继承困境

发布于 2024-08-06 09:11:29 字数 4670 浏览 2 评论 0原文

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

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

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

感谢任何帮助。

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

起初我认为 Managu 的 解决方案将是我的首选之一。然而,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 ... ************** 垃圾 ************** < code>BOOST_MPL_ASSERT 产生。

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

谢谢。

I'm facing problems with the design of a C++ library of mine. It is a library for reading streams that support a feature I haven't found on other "stream" implementations. It is not really important why I've decided to start writing it. The point is I have a stream class that provides two important behaviours through multiple inheritance: shareability and seekability.

Shareable streams are those that have a shareBlock(size_t length) method that returns a new stream that shares resources with its parent stream (e.g. using the same memory block used by parent stream). Seekable streams are those that are.. well, seekable. Through a method seek(), these classes can seek to a given point in the stream. Not all streams of the library are shareable and/or seekable.

A stream class that both provides implementation for seeking and sharing resources inherits interface classes called Seekable and Shareable. That's all good if I know the type of such a stream, but, sometimes, I might want a function to accept as argument a stream that simply fulfills the quality of being seekable and shareable at the same time, regardless of which stream class it actually is. I could do that creating yet another class that inherits both Seekable and Shareable and taking a reference to that type, but then I would have to make my classes that are both seekable and shareable inherit from that class. If more "behavioural classes" like those were to be added, I would need to make several modifications everywhere in the code, soon leading to unmaintainable code. Is there a way to solve this dilemma? If not, then I'm absolutely coming to understand why people are not satisfied by multiple inheritance. It almost does the job, but, just then, it doesn't :D

Any help is appreciated.

-- 2nd edit, preferred problem resolution --

At first I thought Managu's solution would be my preferred one. However, Matthieu M. came with another I preferred over Managu's: to use boost::enable_if<>. I would like to use Managu's solution if BOOST_MPL_ASSERT produced messages weren't so creepy. If there was any way to create instructive compile-time error messages, I would surely do that way. But, as I said, the methods available produce creepy messages. So I prefer the (much) lesser instructive, yet cleaner message produced when boost::enable_if<> conditions are not met.

I've created some macros to ease the task to write template functions that take arguments inheriting select class types, here they go:

// 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&)''
}

As you can see, the error messages are exceptionally clean (at least in GCC 3.4.5). But they can be misleading. It doesn't inform you that you've passed the wrong argument type. It informs you that the function doesn't exist (and, in fact, it doesn't due to SFINAE; but that may not be exactly clear to the user). Still, I prefer those clean messages over those randomStuff ... ************** garbage ************** BOOST_MPL_ASSERT produces.

If you find any bugs in this code, please edit and correct them, or post a comment in that regard. The one major issue I find in those macros is that they're limited to some Boost.Preprocessor limits. Here, for example, I can only pass a DerivationsArray of up to 4 items to SONETTO_ENABLE_IF_DERIVED(). I think those limits are configurable though, and maybe they will even be lifted in upcoming C++1x standard, won't they? Please, correct me if I'm wrong. I don't remember if they have suggested changes to the preprocessor.

Thank you.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

何必那么矫情 2024-08-13 09:11:29

只是一些想法:

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);
}

Just a few thoughts:

STL has this same sort of problem with iterators and functors. The solution there was basically to remove types from the equation all together, document the requirements (as "concepts"), and use what amounts to duck typing. This fits well a policy of compile-time polymorphism.

Perhaps a midground would be to create a template function which statically checks its conditions at instantiation. Here's a sketch (which I don't guarantee will compile).

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);
    ....
}

Edit: Spent a few minutes making sure things compiled, and "cleaning up" the error messages:

#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);
}
贩梦商人 2024-08-13 09:11:29

看看 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 和可寻求。

Take a look at 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);

Thanks to SFINAE this function will only be considered if Stream satisfies the requirement, ie here derive from both Shareable and Seekable.

权谋诡计 2024-08-13 09:11:29

使用模板方法怎么样?

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

How about using a template method?

template <typename STREAM>
void doSomething(STREAM &stream)
{
  stream.share();
  stream.seek(...);
}
廻憶裏菂餘溫 2024-08-13 09:11:29

您可能需要装饰器模式

You might want the Decorator pattern.

垂暮老矣 2024-08-13 09:11:29

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

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

Assuming both Seekable and Shareable have common ancestor, one way I can think of is trying to downcast (of course, asserts replaced with your error-checking):

void foo(Stream *s) {
    assert(s != NULL);
    assert(dynamic_cast<Seekable*>(s) != NULL);
    assert(dynamic_cast<Shareable*>(s) != NULL);
}
海夕 2024-08-13 09:11:29

将“可共享”和“可查找”替换为“输入”和“输出”,然后找到您的“io”解决方案。在图书馆中,类似的问题应该有类似的解决方案。

Replace 'shareable' and 'seekable' with 'in' and 'out' and find your 'io' solution. In a library similar problems should have similar solutions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文