【问题标题】:Opaque pointer to struct with template members指向带有模板成员的结构的不透明指针
【发布时间】:2016-06-25 17:38:05
【问题描述】:

假设我正在构建一个链表(实际的数据结构完全不同,但链表足以解决问题),其节点看起来像

template <typename T>
struct node
{
  struct node<T> *next;
  T data;
};

对于我的数据结构,我有很多返回类型为struct node * 的函数,我希望用户将此类型视为不透明的。在链表示例中,这样的函数可以是例如get_next(struct node&lt;T&gt; *n)insert_after(struct node&lt;T&gt; *x, struct node&lt;T&gt; *y)。只有极少数功能,即分配nodes 或获取/设置其data 字段的功能,需要了解有关T 的任何信息。

有没有更好的方法来“忽略T”并让用户只与typedef struct node * opaque_handle 之类的东西进行交互,以实现那些不必关心T 的功能?我的直觉反应来自 C,只是在void* 之间进行转换,但这听起来不太优雅。

编辑: CygnusX1 的comment 让我相信,我要求类型系统提供太多保证,同时我试图规避太多这些保证。我将退回到让T 成为void *,但会以强制转换和间接为代价。

【问题讨论】:

  • 也许听起来不太优雅,但它已被广泛使用。例如阅读pimpl。在公共标头中有一个前向声明struct T,并在struct node 中使用T*(在这种情况下不需要是模板)
  • 是否有特殊需要以公开node&lt;T&gt; 类型的方式设计您的数据结构?为什么不按照标准中的其他容器建模(用户界面函数返回T 而不是node&lt;T&gt;)?
  • 您可能想看看STL 集合是如何处理这个问题的。它们实际上并没有让最终用户访问实现结构,例如node。他们的接口包含一个可以被最终用户理解的抽象类型,如iteratorvalue_typereference等等。
  • @user2296177:我的意思是完全避免暴露node&lt;T&gt;(对于几乎所有功能)。我想要typedef struct node&lt;T&gt; * handle_t 的东西,并且大多数函数都接受并返回handle_t
  • @gspr 问题是你根本不需要暴露node&lt;T&gt;。从用户的 POV,您的数据结构函数返回 T's。内存管理应该由数据结构或基类在内部完成。就像@WF 建议的那样,只需查看其中一个标准容器即可在界面上获得灵感。

标签: c++ templates opaque-pointers


【解决方案1】:

虽然您不关心 T 是什么,但您最喜欢将它与不同的类型区分开来 - 比如说 U,不是吗? 您可能希望以下内容引发错误:

node<T>* elem1 = ...
node<U>* elem2 = ...
elem1 = elem2

有几种方法可以在不牺牲类型检查或运行时性能的情况下简化代码:

  • 如果您使用 C++11,请考虑使用 auto,而不是在使用函数时显式命名类型
  • 如果node&lt;T&gt;在你的代码中很常见,你可以设置一个全局范围typedef

另请注意,在 node&lt;T&gt; 定义的上下文中,允许使用普通的 node(不带模板参数)。

如果您真的想隐藏 node 的内容,请考虑按照 mvidelgauz 的建议实现 pimpl 模式。

【讨论】:

  • 回答的第一部分:嗯,对于 一些 函数,是的。但是对于很多功能(例如在链表示例中插入一个新的、已分配的节点),绝对不需要知道第一个结构体中的字段的任何信息。我的很多函数only 需要知道该结构以指向节点的指针开头。 T 可能是他们关心的一百万字节的疯狂内容。
  • 关于typedef:但我做不到typedef struct node&lt;T&gt; * foo,可以吗?如果可以的话,我几乎所有的函数都会接受并返回foo 类型,并且会通过知道“foo 是指向node&lt;T&gt; 的指针来工作,我不在乎T 是什么,因为我只需要第一个字段,它始终是指向另一个 node&lt;T&gt;"的指针。
  • @gspr 您仍然需要知道您插入的第二个元素与第一个元素的类型相同——即使它是数百万字节的疯狂内容。
  • 如果它已经分配到别处(当然,由知道T 是什么的人分配),我要不要?
  • @gspr 您需要知道其他人分配的内容与您列表中包含的类型相匹配。考虑一下你可以让node&lt;T&gt;node&lt;U&gt; 类型飞来飞去。您仍然需要确保不会错误地将node&lt;U&gt; 放入T-s 的列表中。 TU 不同的知识很有用。理论上,您可以在不提供实现的情况下使用类型标记绕过它,但是使用普通的 TU 通常更简单。
【解决方案2】:

如果您可以使用 boost,那么 boost::any 或 boost::variant 或许能够帮助实现异构容器。

你追求的是这样的吗?:

#include <iostream>
#include <boost/any.hpp>
#include <list>

using Collection = std::list<boost::any>;
using Node = Collection::iterator;

static Collection anys;

template<typename T>
Node insert_after(T const& obj, Node start_pos = anys.end())
{
    return anys.insert(start_pos, boost::any(obj));
}

void print_type(boost::any const& a)
{
    if (a.type() == typeid(int)) { std::cout << "int" << std::endl; }
    else if (a.type() == typeid(float)) { std::cout << "float" << std::endl; }
}

int main()
{
    const auto node1 = insert_after(int(1));
    const auto node2 = insert_after(float(2.57));
    const auto node3 = insert_after(int(3));

    std::cout << boost::any_cast<int>(*node1) << std::endl;
    std::cout << boost::any_cast<float>(*node2) << std::endl;
    std::cout << boost::any_cast<int>(*node3) << std::endl;

    print_type(*node1);
    print_type(*node2);
    print_type(*node3);

    return 0;
}

输出:

1
2.57
3
int
float
int

【讨论】:

  • 他不想要一个异构容器,他只是想掩盖他的节点是 node&lt;T&gt; 和其他一些没有关于 T 信息的类型的事实。
  • 感谢您的回答,但大多数 boost 对我来说都是黑魔法,除非绝对必要,否则我尽量避免使用它:-)
  • @user2296177 啊,我认为基于其他 cmets OP 想要构建一个其节点可能包含不同 T 的列表,对不起。
猜你喜欢
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多