【发布时间】:2013-02-23 23:19:33
【问题描述】:
我正在开发一个定义接口的 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 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 可能有多种实现)。
或者有没有更好的方法来解决这个问题?我真的不想编写自己的排序实现,尽管它可能不会太难。
【问题讨论】:
-
我不明白为什么字节交换会不好。您需要重新排列对象,因此您需要复制内存(或指向它们的指针)。总的来说,它实际上取决于 opaque 类型的实现。如果它们可以有效地交换,那就没问题了。
-
@pmr - 我可能只是想多了,但交换它们的理想方法是有一个足够大的临时缓冲区来容纳一个 foo,memcpy a 到它,memcpy b 到 a,然后memcpy 将临时缓冲区放入 b。如果按字节进行,则需要在循环中进行 n 字节交换,其中 n 是 FooIter::GetFooSize()
-
是的,但正如我所说:如果你必须做一个手术,你需要做。临时缓冲区将是静态的(它也是静态的,因为您不能交换不同类型的数据)。您可能可以进行(逐字异或交换)[en.wikipedia.org/wiki/XOR_swap_algorithm],但这是否真的会使其更快还有待观察。
-
好吧,我认为实际的答案是自己实现排序,从而允许安全使用一次性分配的缓冲区作为临时缓冲区。但是现在我已经考虑了更多,这确实是过早的优化。