C++ 如何有效地使用std::sort和不透明数据类型?
我正在开发一个SDK,它定义了如下接口C++ 如何有效地使用std::sort和不透明数据类型?,c++,sorting,stl,C++,Sorting,Stl,我正在开发一个SDK,它定义了如下接口 class FooIter { // Move to the next foo, return false if there is none. virtual bool Move() = 0; // Return a pointer to the current foo. virtual const void* GetFoo() = 0; // Get the size of a 'foo', which is
class FooIter
{
// Move to the next foo, return false if there is none.
virtual bool Move() = 0;
// Return a pointer to the current foo.
virtual const void* GetFoo() = 0;
// Get the size of a 'foo', which is a fixed-size POD.
virtual size_t GetFooSize() = 0;
// Get a comparator for foos.
virtual const FooComparator* GetComparator() = 0;
};
class FooComparator
{
virtual int compare(const void* first, const void* second) const = 0;
};
所以基本上,foo是一种不透明类型,我可以将其视为固定长度的二进制缓冲区+和相关的排序函数
现在,我想在将这些foo传递回客户机代码之前对它们进行排序。可能有许多foo,因此我必须实现外部排序,但我想使用std::sort对初始运行进行排序
我想我应该分配一个大小为N*FooIter::GetFooSize()的缓冲区,使用FooIter将其填充为foos,然后在将其写入磁盘之前使用std::sort对其进行排序
我可以从编写迭代器类开始
class FooBufferIter
{
public:
FooBufferIter(const void* fooAddr, int fooSize) : m_fooAddr(fooAddr), m_fooSize(fooSize) {}
FooWrapper operator*() {return FooWrapper(m_fooAddr, m_fooSize);}
FooBufferIter operator++() {return FooBufferIter(m_fooAddr + m_fooSize, m_fooSize);}
// All other needed iterator methods.
private:
const void* m_fooAddr;
int m_fooSize;
};
和一个用于foo内存的包装器类
class FooWrapper
{
public:
FooWrapper(const void* fooAddr, int fooSize) : m_fooAddr(fooAddr), m_fooSize(fooSize) {}
private:
const void* m_fooAddr;
int m_fooSize;
};
我的理解是std::sort将使用std::swap来重新排列序列中的元素。我的问题是,我看不到如何在FooWrapper上专门化std::swap以高效地执行交换(最重要的是,没有动态分配)。我可以一个字节一个字节地交换,但这似乎效率低下
另一种方法是将指针的并行序列排序到我的Foo数组中,但我不想这样做,因为在实践中,Foo可能非常小,因此并行序列可以使用与Foo序列一样多的内存,我想最大限度地增加一次可以排序的指针数量
还有一个很好的ol'qsort可能更适合这种情况,但我不确定如何将FooComparator对象转换为函数指针(FooComparator可能有多个实现)
还是有更好的办法?我真的不想编写自己的排序实现,尽管这可能不会太难。我会构建一个void*缓冲区,对它们进行排序,然后生成输出缓冲区 作为第一步。因为很容易。然后编写所有其他内容,查找性能瓶颈 作为下一步,我将看看是否可以使用完整的类型信息进行内部排序。因为它是最优的 否则,将使用具有专门交换的pod块伪引用迭代器。如果性能测试证明进一步的优化是正确的,那么它将为大的指针和小的数据排序
但是从KISS开始,先做一些必须是硬的部分。我会构建一个void*缓冲区,对它们进行排序,然后生成输出缓冲区 作为第一步。因为很容易。然后编写所有其他内容,查找性能瓶颈 作为下一步,我将看看是否可以使用完整的类型信息进行内部排序。因为它是最优的 否则,将使用具有专门交换的pod块伪引用迭代器。如果性能测试证明进一步的优化是正确的,那么它将为大的指针和小的数据排序
但是从KISS开始,先做一些必须很难的部分。基于FooIter::GetFooSize()的不同实现可能是一种方法……基于FooIter::GetFooSize()的不同实现可能是一种方法……我不明白为什么字节交换会不好。您需要重新排列对象,以便复制内存(或指向它们的指针)。大体上,它实际上取决于不透明类型的实现。如果它们可以有效地交换,您就可以了。@pmr-I可能只是想得太多了,但是交换它们的理想方法是有一个足够大的临时缓冲区来容纳一个foo,memcpy a到它,memcpy b到a,然后memcpy临时缓冲区到b。如果按字节执行,则需要在循环中执行n个字节交换,其中n是FooIter::GetFooSize(),是的,但正如我所说的:如果必须执行操作,则需要执行该操作。临时缓冲区将是静态的(它也有,因为您不能交换不同类型的数据)。您可能可以执行(逐字异或交换)[但这是否真的能让它更快还有待观察。好吧,我认为实际的答案是自己实现排序,从而允许安全地使用曾经分配的临时缓冲区。但现在我考虑得更多,这真的是过早的优化。我不明白为什么字节交换会不好。你需要重新排列对象,以便你需要复制内存(或指向内存的指针)。总的来说,这实际上取决于不透明类型的实现。如果它们可以有效地交换,那就没问题了。@pmr-我可能只是想得太多了,但是交换它们的理想方法是有一个足够大的临时缓冲区来容纳一个foo,memcpy a到它,memcpy b到a,然后memcpy临时缓冲区到b。如果你这样做了byte-wise,您需要在循环中进行n字节交换,其中n是FooIter::GetFooSize(),是的,但正如我所说的:如果必须执行操作,则需要执行该操作。临时缓冲区将是静态的(它也是静态的,因为您无法交换不同类型的数据)。您可能可以执行(逐字异或交换)[但这是否真的能让它更快还有待观察。好吧,我认为实际的答案是自己实现排序,从而允许安全地使用曾经分配的临时缓冲区。但现在我考虑得更多,这确实是过早的优化。