C++:STL 类型和自定义类型的优雅"polymorphism"?

C++: elegant "polymorphism" with STL types and custom types?

本文关键字:类型 polymorphism 自定义 STL C++      更新时间:2023-10-16

假设我有一个名为my_vectorstd::vector的自定义实现,并且它们具有相同的接口。

有许多函数将std::vector(或其指针/引用)作为输入。我想对这些功能进行尽可能少的修改,同时允许它们接受std::vectormy_vector

用函数模板替换这些函数是一种方法,但似乎有很多变化(因此很难回滚),除非我使用std::is_same使它们进一步复杂化,否则它们无法进行类型检查。函数重载是另一种选择,但它需要复制代码,并且也很难回滚更改。

:我可以为std::vectormy_vector设计一个包装类型Vector,并让这些函数接受Vector(或其指针/引用)吗?但多态性的问题在于std::vector不能继承自定义类。

理想情况下,代码应类似于以下内容(不需要相同):

功能定义:

// void foo(std::vector<char> vec) {...} // old
void foo(Vector<char> vec) {...}  // new

使用函数:

std::vector<char> stdvec(5,'a');
my_vector<char> myvec(5,'a');
...
foo(stdvec);
foo(myvec);

谢谢!

PS1:接受迭代器而不是容器的对象是一个很好的建议。但是,有些操作是迭代器无法实现的(例如,push_back()resize()reserve()):

void extendVectorAndAddGarbage(std::vector<char> &vec) {
size_t so=vec.size();
vec.reserve(sz*100);
for (size_t i=sz; sz<sz*100;++sz) {...}
}

PS2:容器可能不std::vector- 我问的是关于在具有相同接口的 STL 类型和自定义类型上使用"多态性"。

PS3:如果您认为无法实现,请不要犹豫,写下答案。

问题:我可以为 std::vector 和 my_vector,并让这些函数接受 Vector(或其 指针/引用)代替?但是多态性的问题在于 std::vector 不能继承自定义类。

我真的是OO的粉丝,继承和多态性以及所有那些很酷的东西,但更重要的是,我不喜欢在不需要的时候不做OO。是的,当然你可以编写一个包装器,让你多态地使用任何类型,你可以使用继承(如果你愿意,甚至可以使用多个),但你为什么要这样做呢?你真的想写所有这些样板吗?

如果您希望对现有代码进行最少的更改,例如

// void foo(std::vector<char> vec) {...} // old
void foo(Vector<char> vec) {...}  // new

然后使其成为模板:

template <typename T> void foo(T vec) {...}

对于您的示例,那将是

template <typename T> 
void foo(T &vec) {
size_t so=vec.size();
vec.reserve(sz*100);
for (size_t i=sz; sz<sz*100;++sz) {...}
}

如果您真的想防止自己使用与std::vectormy_vector不同的内容实例化模板,那么您可以这样做:

namespace {
template <typename T> 
void foo_impl(T& t) {
size_t so=vec.size();
vec.reserve(sz*100);
for (size_t i=sz; sz<sz*100;++sz) {...}
}
}
void foo(std::vector& t) { foo_impl(t);}
void foo(my_vector& t) { foo_impl(t);}

你应该做的第一件事就是去写或gsl::spangsl::span<const T>std::vector<T,A> const&的一流替代品。

您经常要做的下一件事是将数据附加到向量中。

template<class T>
struct sink_t {
using impl=std::function<void(T&&)>;
impl fn;
using res_fn=std::function<void(std::size_t)>;
res_fn res;
void reserve(std::size_t n) { if(res) res(n); }
void push_back(T const& t) {
push_back( T(t) );
}
void push_back( T&& t ) {
fn(std::move(t));
}
void operator()(T&& t)const { push_back(std::move(t)); }
sink_t(impl f, res r={}):fn(std::move(f)), res(std::move(r)) {}
template<class A>
sink_t( std::vector<T, A>& v ):
fn(
[&v](T&& tin){
v.push_back( std::move(tin) );
}
),
res(
[&v](std::size_t n){v.reserve(n);}
)
{}
sink_t( my_vector<T>& v ):
fn(
[&v](T&& tin){
v.push_back( std::move(tin) );
}
),
res(
[&v](std::size_t n){v.reserve(n);}
)
{}
sink_t( T& t ):
fn( [&t]( T&& tin ) { t = std::move(tin); } )
{}
sink_t( T* t ):
fn( t?impl([t]( T&& tin ) { *t = std::move(tin); }):impl() )
{}
explicit operator bool() const { return (bool)fn; }
};

现在,如果您有代码可以通过push_back将内容推入vector<T>,请将其改为sink_t<T>

老实说,这应该处理 99/100 个合理的案例。

例外情况是,如果你的代码同时从容器读取和写入,这通常是一个不好的迹象。

在这些情况下,您可以编写一个方法对方法的矢量类型橡皮擦。 但这样做的成本可能不值得。

我建议你使用上述两种技术,看看你的代码有多少变得微不足道,变得灵活。

gsl::span<T>T的(部分或整体)连续缓冲区的视图。gsl::span<T const>基本上是您所关心的std::vector<T> const&的所有部分,而不需要将其实际存储在特定结构中。

sink_t<T>代表"一个可以扔T的地方"。 我认为在上面的代码中移动T很便宜,而且std::function比涉及功能指针的手动类型擦除有更多的开销,但它应该相当不错。 我给了它.reserve.push_back,因为这应该涵盖您想要的大部分代码;您可以添加.insert_back( Iterator, Iterator ).move_into_back( Container&& ).insert_back( Container const& ),但我不会添加.end(),因为这是一个非常简单的代码转换,可以用vec.insert_back( start, finish )替换vec.insert( vec.end(), start, finish )

最后一种可能性是你的代码正在做一个std::vector<T>& vec,它所做的只是分配给那个vec(或清除它并分配给它)。 这是一点代码气味,但您可以通过使用一个薄包装器来修复它,该包装器获取容器,清除它,然后将其传递给基于接收器的函数版本。

这是我在自己的代码中使用的一种技术,用于抽象出"我放东西的地方"。 它工作得相当好。


作为一件特别有趣的事情,如果你有像这样执行管道处理的代码:

template<class In, class Out>
using pipeline = std::function< void( In&&, sink_t<Out> ) >;

您可以撰写它们

template<class In, class Out>
sink_t<In> operator|( pipeline<In, Out> pipe, sink_t<Out> sink ) {
return
[pipe=std::move(pipe), sink=std::move(sink)]( In&& in ){
pipe( std::move(in), sink );
};
}
template<class In, class Mid, class Out>
pipeline<In, Out> operator|( pipeline<In, Mid> lhs, pipeline<Mid, Out> rhs ) {
return [lhs=std::move(lhs), rhs=std::move(rhs)]( In&& in, sink_t<Out>  sink){
return lhs( std::move(in), std::move(rhs)|std::move(sink) );
};
}

这很有趣。