C++:STL 类型和自定义类型的优雅"polymorphism"?
C++: elegant "polymorphism" with STL types and custom types?
假设我有一个名为my_vector
的std::vector
的自定义实现,并且它们具有相同的接口。
有许多函数将std::vector
(或其指针/引用)作为输入。我想对这些功能进行尽可能少的修改,同时允许它们接受std::vector
或my_vector
。
用函数模板替换这些函数是一种方法,但似乎有很多变化(因此很难回滚),除非我使用std::is_same
使它们进一步复杂化,否则它们无法进行类型检查。函数重载是另一种选择,但它需要复制代码,并且也很难回滚更改。
问:我可以为std::vector
和my_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::vector
或my_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::span
。gsl::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) );
};
}
这很有趣。
- ArduinoJson 6.15.2:JsonObject没有命名类型
- 防止主数据类型C++的隐式转换
- 大量序列中核苷酸类型的快速计数
- 如何从C++中的依赖类型中获得它所依赖的类型
- 有关插入适配器的错误。[错误]请求从 'back_insert_iterator<vector<>>' 类型转换为非标量类型
- 是否可以初始化不可复制类型的成员变量(或基类)
- 如何获取std::result_of函数的返回类型
- 从父命名空间重载类型
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- Openssl 1.1.1d无效使用不完整的类型"struct dsa_st"
- 访问者访问变体并返回不同类型时出错
- 在VS2010-VS2015下编译时,如何使用decltype作为较大类型表达式的LHS
- 处理小于cpu数据总线的数据类型.(c++转换为机器代码)
- C++ 雷神库 - 使用资源加载器类时出现问题(不命名类型)
- 模板元程序查找相似的连续类型名称
- 是否可以从int转换为enum类类型
- 构造函数正在调用一个使用当前类类型的函数
- C++:STL 类型和自定义类型的优雅"polymorphism"?
- 使用派生类型为基类型的模板化参数调用函数时"template polymorphism"?