我遇到了我的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<>
条件不满足的情况下产生的(更多)较少的指导性但更清晰的信息。
我已经创建了一些宏来简化任务来编写模板函数,这些函数接受继承select类类型的参数,在这里它们会出现:
// 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限制。例如,在此处,我只能将DerivationsArray
最多4个项目传递给SONETTO_ENABLE_IF_DERIVED()
。我认为这些限制是可配置的,也许它们甚至可以在即将推出的C ++ 1x标准中解除,不是吗?如果我错了,请纠正我。我不记得他们是否建议对预处理器进行更改。
谢谢。
答案 0 :(得分:12)
只是一些想法:
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 :(得分:6)
// 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满足要求时,才会考虑此功能,即此处来自可共享和可搜索。
答案 2 :(得分:1)
使用模板方法怎么样?
template <typename STREAM>
void doSomething(STREAM &stream)
{
stream.share();
stream.seek(...);
}
答案 3 :(得分:0)
您可能需要Decorator pattern。
答案 4 :(得分:0)
假设Seekable
和Shareable
都有共同的祖先,我可以想到的一种方法是尝试向下转换(当然,assert
替换为您的错误检查):
void foo(Stream *s) {
assert(s != NULL);
assert(dynamic_cast<Seekable*>(s) != NULL);
assert(dynamic_cast<Shareable*>(s) != NULL);
}
答案 5 :(得分:0)
将'shareable'和'seekable'替换为'in'和'out',找到你的'io'解决方案。在图书馆中,类似的问题应该有类似的解决方案。