C ++:有没有一种有效的方法来使用指针,语法等指针?

时间:2016-01-17 06:32:10

标签: c++ pointers

这是我的旅程,试图为我怀疑相对经常弹出的问题获得一些零开销的句法方便。我是C ++的新手,所以我希望有人可以为我提供更好的解决方案。给出的所有代码都应该在C ++ 11下编译和运行。还要注意,虽然我的解决方案涉及使用指针的语法,但我真的很好,“就像使用指针一样简单。”

我有一些代码使用的对象网络互相指针,如下所示。 (这里的例子只是一个简单的链表,但实际结构更复杂 - half-edge mesh。)

#include <iostream>

struct Node {
  int val;
  Node* next;
};

int main()
{
  Node* a = new Node();
  Node* b = new Node();

  a->val = 1;
  b->val = 2;
  a->next = b;

  std::cout << "Sum: " << a->val + a->next->val << std::endl;
}

出于效率原因,正在更改此代码,以便将对象全部存储在中央列表中,并通过索引进行访问。 (我知道还有其他选项,比如内存池可以让我继续使用实际的指针。)

值得注意的是,我不能使用迭代器或指针进入中心列表,因为列表通常会被修改很多,并且(据我所知)指针和迭代器可能会失效。

#include <iostream>
#include <vector>

struct Node {
  int val;
  std::size_t next;
};

int main()
{
  std::vector<Node> nodeList {2};
  std::size_t a = 0;
  std::size_t b = 1;

  nodeList[a].val = 1;
  nodeList[b].val = 2;
  nodeList[a].next = b;

  std::cout << "Sum: " 
            << nodeList[a].val + nodeList[nodeList[a].next].val
            << std::endl;
}

显然,这种变化会产生语法上的代价。在Scala中,我可以用隐含来解决这个问题。在C ++中,我能想到的最明显的事情是创建一个知道索引和数组的包装器对象,如下所示:

#include <iostream>
#include <vector>

template<typename T, typename AofT = std::vector<T>>
struct IndexPtr {
  std::size_t index;
  AofT* collection;

  IndexPtr() = default;
  IndexPtr(std::size_t index, AofT& collection)
    : index(index), collection(&collection) 
  { /* no more initialization to do */ }

  T& operator*() { return (*collection)[index]; }
  T* operator->() { return &(*collection)[index]; }
};

struct Node {
  int val;
  IndexPtr<Node, std::vector<Node>> next;
};

int main()
{
  std::vector<Node> nodeList {2};
  auto a = IndexPtr<Node>(0, nodeList);
  auto b = IndexPtr<Node>(1, nodeList);

  a->val = 1;
  b->val = 2;
  a->next = b;

  std::cout << "Sum: " << a->val + a->next->val << std::endl;
}

但是,现在我为每个索引存储一个额外的指针只是为了方便语法。不可接受,特别是因为我注重表现。所以我能想到的另一件事就是用静态类成员来模拟隐式参数:

#include <iostream>
#include <vector>

template<typename T, typename AofT = std::vector<T>>
struct IndexPtr {
  static AofT* collection;

  std::size_t index;

  IndexPtr() = default;
  IndexPtr(std::size_t index) : index(index) {}

  T& operator*() { return (*collection)[index]; }
  T* operator->() { return &(*collection)[index]; }
};

template<typename T, typename AofT>
AofT* IndexPtr<T, AofT>::collection = nullptr;

struct Node {
  int val;
  IndexPtr<Node, std::vector<Node>> next;
};

int main()
{
  std::vector<Node> nodeList {2};
  IndexPtr<Node>::collection = &nodeList;

  auto a = IndexPtr<Node>(0);
  auto b = IndexPtr<Node>(1);

  a->val = 1;
  b->val = 2;
  a->next = b;

  std::cout << "Sum: " << a->val + a->next->val << std::endl;
}

这是我提出的最好的,而且有点不满意。

  • 如果您在使用任何collection s之前忘记初始化IndexPtr变量,那么一切都会崩溃。
  • 你不能让IndexPtr同时使用不同的数组(很容易),但我对此基本上没问题。
  • 我不知道这种方法是否有任何特殊的性能损失。

思想?

3 个答案:

答案 0 :(得分:3)

在考虑到Sergey Tachenov的答案一段时间后,我认为可以做出合理的妥协。有一个包装器类,它知道索引和集合。它仍然有像next()这样的方法返回另一个包装器。但是,我们还允许使用operator*operator->来获取原始的未包装值。

因此,我们不必使用复杂的方案来支持

a.next().next() = b;

相反,我们通常只在链的末尾使用->。因此我们可以完全正常地修改或使用该值。

a.next()->next = b;

以下是使用此方法编写的示例代码。

#include <iostream>
#include <vector>

struct Node {
    int val;
    std::size_t next;
};

class IndexPointer {
    std::size_t index;
    std::vector<Node>& collection;

public:
    IndexPointer(std::size_t index, std::vector<Node>& collection)
        : index(index), collection(collection)
    { /* done */ }

    inline operator std::size_t() {return index;}

    inline Node& operator*() {return collection[index];}
    inline Node* operator->() {return &collection[index];}
    inline IndexPointer next() {return IndexPointer((*this)->next, collection);}
};

class NodeList {
    std::vector<Node> nodes;

public:
    NodeList(std::size_t n) : nodes(n) {}
    inline IndexPointer operator[](std::size_t i) {return IndexPointer(i, nodes);}
};

int main()
{
    NodeList nodes {2};

    auto a = nodes[0];
    auto b = nodes[1];

    a->val = 1;
    a->next = b;
    a.next()->val = 2;

    std::cout << "Sum: " << a->val + a.next()->val << std::endl;
}

答案 1 :(得分:2)

我真的不喜欢static方法。你似乎意识到当你必须使用两个或更多的集合并且你永远不知道你不会赢得它时,这是一个巨大的问题。这只是一个糟糕的设计。

在给出了很多想法之后,我想出了这个疯狂的解决方案:

class NodeList {
    friend class NodeReference;
    friend class NextReference;
private:
    struct Node {
        int val;
        std::size_t next;
    };
    std::vector<Node> nodes;
public:
    NodeList(std::size_t size) :
        nodes(size) {}
    inline NodeReference operator[](std::size_t index);
};

class NodeReference {
    friend class NodeList;
    friend class NextReference;
public:
    int &val() { return list.nodes[index].val; }
    inline NextReference next();
private:
    NodeList &list;
    std::size_t index;
    NodeReference(NodeList &list, std::size_t index) :
        list(list), index(index) {}
};

class NextReference {
    friend class NodeReference;
public:
    int &val()
    {
        return node.val();
    }
    NextReference operator=(NodeReference &node)
    {
        node.list.nodes[node.index].next = node.index;
        return *this;
    }
private:
    NodeReference &node;
    NextReference(NodeReference &node) :
        node(node) {}
};

inline NodeReference NodeList::operator[](std::size_t index)
{
    return NodeReference(*this, index);
}

inline NextReference NodeReference::next()
{
    return NextReference(*this);
}

int main() {
    NodeList nodeList(2);
    auto a = nodeList[0];
    auto b = nodeList[1];
    a.val() = 1;
    b.val() = 2;
    a.next() = b;

    std::cout << "Sum: " << a.val() + a.next().val() << std::endl;
}

但我仍然不喜欢它。语法方面,我认为这很好,但三级引用可能是一个性能问题,而且它看起来太难看了。起初,我计划只使用operator[]动态创建一个节点引用,这样就不必在每个节点中存储大量的列表指针。但后来我对如何实现next()访问器感到困惑。如果要返回NodeReference,那么a.next() = b将如何工作?由于a.next()现在引用了下一个节点,它的operator=应该如何更改next节点的a字段?

<强>更新

我继续观察MSVS 2013在发布模式下生成的程序集:

; 62   :    auto a = nodeList[0];
; 63   :    auto b = nodeList[1];
; 64   :    a.val() = 1;
; 65   :    b.val() = 2;
; 66   :    a.next() = b;
; 67   : 
; 68   :    std::cout << "Sum: " << a.val() + a.next().val() << std::endl;

    mov eax, DWORD PTR _nodeList$[esp+36]
    push    OFFSET ??$endl@DU?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@@Z ; std::endl<char,std::char_traits<char> >
    mov DWORD PTR [eax], 1
    mov eax, DWORD PTR _nodeList$[esp+40]
    mov DWORD PTR [eax+8], 2
    mov eax, DWORD PTR _nodeList$[esp+40]
    mov DWORD PTR [eax+12], 1

所以看起来编译器实际上可以通过所有中间引用来解决它,并且只需将必要的值直接分配给它们所属的位置。但是代码看起来仍然看起来很难看,我无法摆脱这种感觉,它可以变得更容易,辅助类更少。

答案 2 :(得分:1)

如何定义基于int的NodePtr类?

#include <vector>
#include <iostream>

struct Node;

std::vector<Node> nodes;

struct NodePtr {
    int index = -1;
    operator bool() const { return index != -1; }
    Node& operator*() { return nodes[index]; }
    Node* operator->() { return &nodes[index]; }
};

struct Node {
    int val;
    NodePtr next;
};

int main()
{
    nodes.push_back({1});
    nodes.push_back({2});

    NodePtr a{0}, b{1};
    a->next = b;

    std::cout << "Sum: " << a->val + a->next->val << std::endl;
}

它基本上增加了一个额外的间接水平(并且已知这可以解决计算机科学中的任何问题......除了有太多级别的间接问题;-))