恒定时间'缺点'

时间:2011-08-08 01:07:14

标签: c++ algorithm c++11

是否有任何方法(例如,修改参数类型)使以下'cons'函数占用恒定时间而不是O(n)时间。即建立列表应该花费O(n)时间,而不是O(n ^ 2)时间。

我可以在以下条件下这样做:

(1)无动态内存分配
(2)x必须仍然有效(即可能没有提及临时工具)
(3)HEAD类型可能有一个单独编译的构造函数(不可嵌入)

#include <type_traits>

template <typename HEAD, typename TAIL>
struct List
{
  List(HEAD head, TAIL tail) : head(head), tail(tail) {}
  typename std::remove_reference<HEAD>::type head;
  typename std::remove_reference<TAIL>::type tail;
};

template <typename HEAD, typename TAIL>
List<HEAD, TAIL> cons(HEAD head, TAIL tail)
{
  return List<HEAD, TAIL>(head, tail);
}

struct Empty {};

int main()
{
  auto x = cons(1, cons(2, cons(3, Empty())));
  // x should still be valid here
}

如何工作的例子。

编译器知道x的类型,因此在堆栈上分配空间。

所以堆栈看起来像这样:

| Int1 | Int2 | Int3 |
| p    | p+4  | p+8  |

p是任意地址。

编译器创建调用cons(2, cons(3, Empty())),将返回值定向到p+4

cons(2, cons(3, Empty()))内,编译器创建对cons(3, Empty())的调用,将返回值定向到p+8

这样,每次调用cons时,都不需要复制tail

我只是不确定代码,因此编译器可以(例如,允许)进行此优化。如果有另一种获得恒定运行时间的方法,我会很乐意使用它。

2 个答案:

答案 0 :(得分:2)

你似乎正在用更糟糕的std::tuple助手重新发明std::make_tuple。改用它。标准不会为std::tuplestd::make_tuple的转发构造函数提供复杂性保证,但它有点没有实际意义,因为这两者使用完美转发,因此每个元素只需要构建一个move / copy / emplace构造致电std::make_tuple;剩下的就是改组参考文献了。它至少是线性数量的结构。

当然,并不能保证你的编译器能够优雅地处理所有的引用改组,但无论如何你都会在错误的级别进行优化。


出于说明目的,几乎但不完全发生了什么:

template<typename... T>
class tuple {
    T... members; // this is not correct, only here for illustration

    // The forwarding constructor
    template<typename... U>
    explicit
    tuple(U&&... u)
        : member(std::forward<U>(u)...)
    {}
};

template<typename... T>
tuple<typename std::decay<T>::type...>
make_tuple(T&&... t)
{ return tuple<typename std::decay<T>::type...>(std::forward<T>(t)...); }

因此,在致电auto tuple = std::make_tuple(1, 2, 3)时,从int的调用中有三个临时make_tuple,然后在第一次调用{{1}时有三个int&&个x值在std::forward<int>(t)...内部绑定到构造函数的参数,这些参数再次作为make_tuple xvalues转发给int&&的概念性三个成员,这三个成员是从它们构造的。


刚才意识到我所说的构造数量仅适用于std::tuple<int, int, int>的调用,而不是整个表达式std::make_tuple。由于从函数返回元组,它可能需要移动/ RVO到最终变量。它仍然是线性的,它仍然是编译器喜欢优化的东西之一,它仍然是担心优化的错误点。

然而,C ++ 0x充满了善意,它已经可以完成您在答案中描述的内容:

auto tuple = std::make_tuple(...);

int i = 3; std::tuple<int, int, int> tuple = std::forward_as_tuple(1, 2, i); 的来电将返回forward_as_tuple。这个帮助器永远不会返回带有值的元组,只返回引用的“浅”元组。 std::tuple<int&&, int&&, int&>的适当转换构造函数将通过两个移动和一个副本初始化其“成员”。请注意,这种愚蠢(非)优化会使您有可能编写std::tuple<int, int, int>,这是一个包含两个悬空引用的元组。

答案 1 :(得分:0)

我会在调查后回答自己的问题,但如果有人知道更好的方法,我仍然会感兴趣。

(1)将List重命名为TempList (2)使TempList私有的构造函数(包括移动和复制) (3)让cons成为TempList的朋友 (4)将HEADTAIL成员引入参考文献。

现在TempList只保存引用,因此没有副本。这些可能是对临时工具的引用,但是没关系,因为临时工具持续到表达式的结尾,并且因为TempList只有私有构造函数,所以它不能分配给表达式的LHS。只有cons可以创建TempList,并且不能超过创建它的表达式。

现在创建一个函数save或具有TempList效果的函数,并返回一个真实的List,另一种类型的数据按值存储。

所以我们有

auto x = save(cons(1, cons(2, cons(3, Empty()))));

数据将被复制或移动一次(通过保存),整个构造现在为O(n)。只要内联TempListconssave结构就可能会被优化掉。

相关问题