几年前我曾经做过C ++开发,当时我发现很难将模板编程与OOP结合起来。目前我在Swift编程,我尝试做一些我当时努力的事情。
这个Swift代码将说明问题:
// protocol is like Java interface or C++ pure virtual base class
protocol Log {
// want to able to add elements from a collection of Ints, but
// it should be any sort of collection that
// can be treated as a sequence
func add<T: SequenceType where T.Generator.Element == Int>(values: T)
}
class DiscreteLog: Log {
var vals: [Int] = []
func add<T: SequenceType where T.Generator.Element == Int>(values: T) {
for v in values {
vals.append(v)
}
}
}
class ContinousLog: Log {
var vals: [Double] = []
func add<T: SequenceType where T.Generator.Element == Int>(values: T) {
for v in values {
vals.append(Double(v))
}
}
}
// I don't have to know whether the log is Continuous or Discrete
// I can still add elements to it
var log: Log = ContinousLog()
log.add([1, 2, 3])
// and elements can come from any kind of sequence, it does not need
// to be an array
log.add(["four": 4, "five: 5].values)
所以问题是如果C ++代码定义为:
virtual void add(vector<Int> elements>)
然后确定我可以有多个子类实现这个方法,但我永远不会提供任何东西,只有vector作为参数。
我可以尝试使用iterator将其更改为更通用的东西:
virtual void add(vector<Int>::iterator elements>)
但我仍然局限于使用矢量迭代器。所以我想我必须写一些像:
template<typename Iterator>
virtual void add(Iterator elements>)
但是这会产生编译错误,因为虚拟方法不允许基于模板的参数。
无论如何,我想知道在现代C ++中这种事情是否可行。
答案 0 :(得分:0)
C ++模板和C#/ Swift / Java泛型是不同的东西。
他们都是&#34;模式代码&#34;从某种意义上说(它们是生成代码的模式),但C#/ Swift / Java泛型使用类型擦除和“忘记”#34;几乎所有关于他们使用的类型的东西,而C ++模板是大象。大象永远不会忘记。
事实证明,可以让大象忘记,但你必须告诉它。 &#34;忘记&#34;的技术关于类型的细节被称为&#34;类型擦除&#34;或者&#34;运行时间概念&#34;。
所以你想要删除&#34;整数序列&#34;的概念。你想要采用任何类型,只要它是一个整数序列,并且能够迭代它。看似公平。
boost
有这种类型的删除。但谁愿意总是依靠提升?
首先,键入erase an input iterator:
template<class T>
struct input_iterator:
std::iterator<
std::input_iterator_tag, // category
T, // value
std::ptrdiff_t, // distance
T*, // pointer
T // reference
>
{
struct erase {
virtual void advance() = 0;
virtual erase* clone() const = 0;
virtual T get() const = 0;
virtual bool equal(erase const& o) = 0;
virtual ~erase() {}
};
std::unique_ptr<erase> pimpl;
input_iterator(input_iterator&&)=default;
input_iterator& operator=(input_iterator&&)=default;
input_iterator()=default;
input_iterator& operator++() {
pimpl->advance();
return *this;
}
input_iterator operator++(int) {
auto copy = *this;
++*this;
return copy;
}
input_iterator(input_iterator const& o):
pimpl(o.pimpl?o.pimpl->clone():nullptr)
{}
input_iterator& operator=(input_iterator const&o) {
if (!o.pimpl) {
if (pimpl) pimpl->reset();
return *this;
}
pimpl = std::unique_ptr<erase>(o.pimpl->clone());
return *this;
}
T operator*() const {
return pimpl->get();
}
friend bool operator==( input_iterator const& lhs, input_iterator const& rhs ) {
return lhs.pimpl->equal(*rhs.pimpl);
}
friend bool operator!=( input_iterator const& lhs, input_iterator const& rhs ) {
return !(lhs==rhs);
}
template<class It>
struct impl:erase{
It it;
impl(impl const&)=default;
impl(It in):it(std::move(in)){}
virtual void advance() override { ++it; }
virtual erase* clone() const override { return new impl(*this); }
virtual T get() const override { return *it; }
virtual bool equal(erase const& o) override {
return static_cast<impl const&>(o).it == it;
}
};
template<
class It,
class=std::enable_if<
std::is_convertible<
typename std::iterator_traits<It>::reference,
T
>{}
>
>
input_iterator(It it):pimpl( new impl<It>{it} ) {}
}; // input_iterator
接下来,有一个范围模板。这是一个存储非类型擦除迭代器的容器,并且公开足以迭代这些迭代器。
template<class It>
struct range {
It b; It e;
It begin() const { return b; }
It end() const { return e; }
range() = default;
range(It start, It finish):b(std::move(start)),e(std::move(finish)) {};
range(range&&)=default;
range(range const&)=default;
range& operator=(range&&)=default;
range& operator=(range const&)=default;
template<class R,
class R_It=std::decay_t<decltype(std::begin(std::declval<R>()))>,
class=std::enable_if< std::is_convertible<R_It, It>{} >
>
range( R&& r ):
range(std::begin(r), std::end(r))
{} // TODO: enable ADL begin lookup
};
上面的类型非常基本:C ++ 1z和boost
都有更好的类型,就像我自己的代码库中一样。但它足以处理for(:)
循环,以及从具有兼容迭代器的容器进行隐式转换。
最后我们的序列类型:
template<class T>
using sequence_of = range<input_iterator<T>>;
等等,那是吗?很好,那些类型构成很好!
除非出现错误,否则我们已经完成了。
您的代码现在需要sequence_of<int>
,他们可以传递std::vector<int>
或std::list<int>
或其他任何内容。
input_iterator类型 - 擦除类型 - 删除所有迭代器,直到通过T
,*
,复制和==
前进获得++
,这对于a for(:)
循环。
range<input_iterator<int>>
将接受其迭代器可以转换为input_iterator<int>
的任何可迭代范围(包括容器)。
缺点?我们刚刚介绍了一堆开销。每种方法都经过虚拟调度,从++
到*
再到==
。
这是(粗略地)泛型所做的事情 - 他们键入 - 删除你在泛型子句中给出的要求。这意味着他们正在处理抽象对象,而不是具体对象,所以他们不可避免地会受到这种间接性能的惩罚。
C ++模板可用于生成类型擦除,甚至还有工具(boost有一些)可以使它更容易。我上面做的是半手动的。类似的技术在std::function<R(Args...)>
中使用,其类型删除到(概念上){copy,call with(Args ...)return R,destroy}(加上一些杂项)。
(上面的代码自由使用C ++ 14。)
所以C ++等价物Log
是:
struct Log {
virtual void add(sequence_of<int>) = 0;
virtual ~Log() {}
};
现在,上面的类型擦除代码有点难看。公平地说,我只是在C ++中实现了一种语言功能而没有直接的语言支持。
我已经看到了一些在C ++中使类型擦除更容易的建议。我不知道这些提案的状态。
如果你想自己动手,这里就是一个简单的&#34;通过3个步骤进行类型擦除的方法:
首先,确定要删除的操作。写相当于input_iterator<T>
- 给它做一堆你想要的方法和操作符。稀疏。将其称为&#34;外部类型&#34;。理想情况下,此类型中的 nothing 为virtual
,它应该是常规或半规则类型(即,它应该像值一样,或者只移动值)。除了界面之外,别实施任何东西。
其次,写一个内部类erase
。它为一组功能提供纯虚拟接口,可以提供外部类型所需的功能。
在外部类型中存储unique_ptr<erase> pimpl;
。将您在外部类型中公开的方法转发到pimpl;
。
第三,写一个内部template<class X> class impl<X>:erase
。它存储变量X x;
,并通过与erase
交互来实现X
中的所有内容。它应该可以从X
构建(可选的完美转发)。
然后,您可以为通过pimpl
创建其new impl<X>(whatever)
的外部类型创建一个完美的转发构造函数。理想情况下,它应该通过SFINAE技术检查其参数是否有效,但这只是实现问题的合格。
现在外部类型&#34;擦除&#34;它构造的任何对象的类型&#34;到#34;你暴露的行动。
现在,对于您的实际问题,我写array_view
或窃取std::experimental::array_view
,并将我的输入限制为该类型数据的任何类型的连续缓冲区。这是更高效的,除非你确实需要,否则接受任何序列都是过度工程的。