C++ 具有特殊情况实现的迭代器
有没有一种方法可以构建一个迭代器类,该迭代器类有两种实现:一种是包含任意数量元素的容器的通用实现,另一种是在容器包含单个元素时使用虚拟函数和动态多态性的特例(非常快)实现 目前,我有:C++ 具有特殊情况实现的迭代器,c++,c++11,boost,iterator,c++17,c++14,C++,C++11,Boost,Iterator,C++17,C++14,有没有一种方法可以构建一个迭代器类,该迭代器类有两种实现:一种是包含任意数量元素的容器的通用实现,另一种是在容器包含单个元素时使用虚拟函数和动态多态性的特例(非常快)实现 目前,我有: struct Container { struct FastIterator; struct SlowIterator; void add(...) { ... } SlowIterator begin_slow() { ... } FastIterator begin_
struct Container {
struct FastIterator;
struct SlowIterator;
void add(...) { ... }
SlowIterator begin_slow() { ... }
FastIterator begin_fast() { ... }
};
相反,我希望:
struct Container {
struct Iterator;
void add(...) { ... }
Iterator begin() { // select between fast and slow based on the contents of the container }
};
以便:
void f() {
Container c;
c.add(...);
Container::Iterator it = c.begin(); // uses FastIterator hidden by the Iterator type
}
void f2() {
Container c;
c.add(...);
c.add(...);
Container::Iterator it = c.begin(); // use SlowIterator hidden by the iterator type
}
当然,显而易见的方法是在迭代器实现中使用虚函数或委托从一种情况切换到另一种情况,但是我测试过,与直接使用慢/快迭代器相比,这会大大降低迭代的速度
由于决定使用哪个实现的所有信息都在调用begin()期间可用,因此我认为有一种方法可以使用某种编译时多态性/技巧来避免任何间接寻址
另外,我真的不希望用户必须决定是调用begin\u fast()还是begin\u slow(),这应该由迭代器类自动处理和隐藏
有办法吗
谢谢当然可以
您的容器成为两种不同状态的std::variant
,“单元素”状态和“多元素”状态(可能还有“零元素”状态)
成员函数add
可以将零元素或单元素容器转换为单元素或多元素函数。类似地,在某些情况下,remove
可能会起到相反的作用
变体本身没有开始
或结束
。相反,用户必须使用一个函数对象访问它,该函数对象可以接受任何一种
template<class T>
struct Container:
std::variant<std::array<T,0>, std::array<T,1>, std::vector<T>>
{
void add(T t) {
std::visit(
overload(
[&](std::array<T,0>& self) {
*this = std::array<T,1>{{std::move(t)}};
},
[&](std::array<T,1>& self) {
std::array<T,1> tmp = std::move(self);
*this = std::vector<T>{tmp[0], std::move(t)};
},
[&](std::vector<T>& self) {
self.push_back( std::move(t) );
}
),
*this
);
}
};
重载
在以下情况下要容易得多:
对于0和1的情况,编译器将确切地知道循环的大小
在中,您必须编写一个外部模板函数对象,而不是内联lambda
您可以将变量
部分移出容器
并移入开始
返回的内容(在迭代器内部),但这需要复杂的分支迭代器实现或调用方访问迭代器。由于begin/end迭代器类型可能是绑定的,因此无论如何,您都希望返回一个范围,这样访问才有意义。无论如何,这会让你回到容器解决方案的一半
您也可以在variant
之外实现此功能,但通常情况下,对变量的早期操作不能更改同一代码范围内的后期类型。它可以用于在“延续传递样式”中传递的可调用对象上进行分派,其中两个实现都将被编译,但在运行时选择一个(通过分支)。编译器可能会意识到访问将进入哪个分支,死代码会消除另一个分支,但另一个分支仍然需要是有效代码
如果您想要完全动态类型化的对象,您将损失至少2到10倍的速度(这是支持此功能的语言所做的),这很难通过单元素循环的迭代效率来恢复。这与在返回的迭代器中存储变量等价物(可能是一个虚拟接口或任何东西)有关,并使其在运行时复杂地处理分支。因为你的目标是表现,这是不现实的
理论上,C++可以根据操作变量来改变变量的类型。也就是说,一种理论语言
Container c;
为“空容器”类型,则:
现在c
将静态类型更改为“单元素容器”,然后
并且c
将静态类型更改为“多元素容器”
<>但是这不是C++模型。您可以像上面那样(在运行时)模拟它,但它不同。当然
您的容器成为两种不同状态的std::variant
,“单元素”状态和“多元素”状态(可能还有“零元素”状态)
成员函数add
可以将零元素或单元素容器转换为单元素或多元素函数。类似地,在某些情况下,remove
可能会起到相反的作用
变体本身没有开始
或结束
。相反,用户必须使用一个函数对象访问它,该函数对象可以接受任何一种
template<class T>
struct Container:
std::variant<std::array<T,0>, std::array<T,1>, std::vector<T>>
{
void add(T t) {
std::visit(
overload(
[&](std::array<T,0>& self) {
*this = std::array<T,1>{{std::move(t)}};
},
[&](std::array<T,1>& self) {
std::array<T,1> tmp = std::move(self);
*this = std::vector<T>{tmp[0], std::move(t)};
},
[&](std::vector<T>& self) {
self.push_back( std::move(t) );
}
),
*this
);
}
};
重载
在以下情况下要容易得多:
对于0和1的情况,编译器将确切地知道循环的大小
在中,您必须编写一个外部模板函数对象,而不是内联lambda
您可以将变量
部分移出容器
并移入开始
返回的内容(在迭代器内部),但这需要复杂的分支迭代器实现或调用方访问迭代器。由于begin/end迭代器类型可能是绑定的,因此无论如何,您都希望返回一个范围,这样访问才有意义。无论如何,这会让你回到容器解决方案的一半
您也可以在variant
之外实现此功能,但通常情况下,对变量的早期操作不能更改同一代码范围内的后期类型。它可以用于在“延续传递样式”中传递的可调用对象上进行分派,其中两个实现都将被编译,但在运行时选择一个(通过分支)。编译器可能会意识到访问将进入哪个分支,死代码会消除另一个分支,但另一个分支仍然需要是有效代码
如果您想要完全动态类型化的对象,您将损失至少2到10倍的速度(这是支持此功能的语言所做的),这很难通过单元素循环的迭代效率来恢复。这与在返回的迭代器中存储变量等价物(可能是一个虚拟接口或任何东西)有关,并使其在r处复杂地处理分支
Container<int> bob = get_container();
std::visit( [](auto&& bob){
for (int x:bob) {
std::cout << x << "\n";
}
}, bob );
Container c;
c.add(foo);
c.add(foo);