【问题标题】:Using std::shared_ptr<void> to point to anything使用 std::shared_ptr<void> 指向任何东西
【发布时间】:2014-05-04 11:12:40
【问题描述】:

我在我的应用程序中使用std::shared_ptr&lt;void&gt; 来创建一个智能指针,它可以指向许多不同类型的数据结构,如结构、向量、矩阵......基本上任何东西。我正在尝试做的是将一些名称映射到它们的数据结构。

我正在使用哈希表执行映射:

std::unordered_map&lt;std::string, std::shared_ptr&lt;void&gt;&gt;

我可以将find()返回的std::shared_ptr&lt;void&gt;转换回std::shared_ptr&lt;my_type&gt;吗?如果有,怎么做?

更重要的是,这是一种好的做法吗?随着应用程序的扩展,这是否会增加太多的复杂性?或者,还有其他完全不同的优雅方法吗?

编辑

可能无法使用 `Boost.Any',因为它使用 RTTI。

也不能为所有这些数据结构使用基类,因为其中一些是像std::vector 这样的 STL 容器。

关于下面的答案中讨论的shared_ptr 删除问题,我读到 shared_ptr 执行 类型擦除 并存储类型信息以知道要调用哪个析构函数。

Shared void pointers. Why does this work?

Why do std::shared_ptr<void> work

Why is shared_ptr<void> not specialized?

但我不确定。

【问题讨论】:

标签: c++ c++11 stl shared-ptr smart-pointers


【解决方案1】:

这不是一个好习惯。如果你没有在你的std::shared_ptr 旁边存储额外的类型信息(你可以使用static_pointer_cast 进行转换),那么你就会有未定义的行为。也许Boost.Any 适合您?

如果您想坚持使用std::shared_ptr&lt;void&gt;,请记住提供自定义删除器功能(请参阅make shared_ptr not use delete)。

【讨论】:

  • std::shared_ptr&lt;void&gt; 实际上并没有调用 UB。
  • 不是自己,而是将指针转换为另一种类型(即p 指向Base 对象但被转换为Derived),然后解除引用。
  • @filmor 谢谢,能解释一下UB是怎么引入的吗?另外,我觉得我必须考虑 Boost.Any 的事实表明我的设计存在根本缺陷。如果我没记错的话,Boost.Any 会引入动态的基于类型的行为,这确实与 C++ 的观点背道而驰,而且速度令人眼花缭乱。
  • @filmor 我现在实际上正在查看一些 C 代码,它使用 void* 指针实现了这一点。但我真的不想在 C++ 中使用原始指针!
  • 仅仅为了它而使用std::shared_ptr 在这里没有帮助。 std::shared_ptr 仅在您具有正确的删除行为时才有意义(否则引用计数是什么?)。您必须将类型信息存储在某个地方才能取消引用指针。无论是在 Boost.Any 内部还是在某个变量中,都无关紧要,但是,我希望 Boost.Any 的实现效率更高:)
【解决方案2】:

如果你存储void*,那么在每个使用点你都需要知道你输入的确切类型,因为cast-to-void*-and-back 仅在你去/来自确切的相同的类型(例如,不是从无到基派生的)。

鉴于每个使用点都需要知道存储指针的类型,为什么要往返于 void 呢?

您存储的每种类型都有一张地图。与每个应用程序一个映射相比,这具有适度的开销(运行时 O(类型数)内存,类似的编译时间)。

使用 C++ templates 几乎没有代码重复。

【讨论】:

  • 我认为我低估了拥有多张地图的想法。非常感谢,我试试看。
  • 您可以消除检查所有不同地图的需要,只需多一张地图即可告诉您对象是哪种类型,与其存储分开。
【解决方案3】:

我可以将 find() 返回的 std::shared_ptr 转换回 std::shared_ptr 吗?如果有,怎么做?

是的。使用pointer casts。也就是说,在这种情况下,您需要将 void* 转换为具体类型这一事实很可能是设计不佳的症状。

您应该(几乎)永远不需要从 void* 转换为强类型指针类型(“几乎”意味着我认为您永远不应该这样做,但可能有些情况我没有考虑过)。

更重要的是,这是一种好的做法吗?

没有。根据经验,只有当您对内存地址的值感兴趣且对存储的数据完全不感兴趣时​​,才应转换为 void*。

随着应用程序的扩展,这是否会过度增加复杂性?

我能想到的第一个问题是您的类型不再定义在一个地方。这意味着,如果你到处都有指针转换,当你决定你的类名改变了,(或者你不再使用 std::string 作为路径而是使用 boost::path 对象等等),您将不得不遍历代码并更新您添加的所有类型转换中的所有类型。

第二个问题是,随着应用程序的扩展,它会携带这个转换问题并将其传播到代码库中,并使整个代码库的情况变得更糟。由于这个原因,您的应用程序代码将变得稍微(或不那么轻微)难以维护。如果您在整个代码中存在其他设计妥协,超过一定大小,您的应用程序将变得难以维护/令人望而却步。

这种方法只会让您无法编写松散耦合的代码。

或者,还有其他完全不同的优雅方法吗?

检查您的代码并列出您在指针上使用的操作。然后,将这些操作作为纯虚函数添加到基类中。根据存储值类型,实现此基类的具体模板特化。

代码:

class pointer_holder { // get a better name
public:
    virtual void teleport_pointer() = 0; // example operation
    virtual ~pointer_holder() = 0;
};

template<typename T>
class type_holder: public pointer_holder {
public:
    // TODO: add constructor and such
    virtual void teleport_pointer() {
        // implement teleport_pointer for T
    }
    virtual ~type_holder();
private:
    T value_;
};

客户端代码:

std::unordered_map<std::string, std::shared_ptr<pointer_holder>> your_map;
your_map["int"] = new type_holder<int>{10};
your_map["string"] = new type_holder<std::string>{"10"};

your_map["int"]->teleport_pointer(); // means something different for each
                                     // pointer type

【讨论】:

  • 如果数据结构没有相同的操作集怎么办?
  • 然后在基类中实现这些操作并让它们抛出异常(throw std::runtime_error("teleport_pointer not valid for type");)。这样,如果为这些类型调用了这些操作,您就会知道。
【解决方案4】:

你可以。

#include <memory>
#include <iostream>
#include <string>

using namespace std;

class wild_ptr {
    shared_ptr<void> v;
public:
    template <typename T>
    void set(T v) {
        this->v = make_shared<T>(v);
    }

    template <typename T>
    T & ref() {
        return (*(T*)v.get());
    }
};

int main(int argc, char* argv[])
{
    shared_ptr<void> a;
    a = make_shared<int>(3);
    cout << (*(int*)a.get()) << '\n';
    cout << *static_pointer_cast<int>(a) << '\n'; // same as above

    wild_ptr b;
    cout << sizeof(b) << '\n';
    b.set(3);
    cout << b.ref<int>() << '\n';
    b.ref<int>() = 4;
    cout << b.ref<int>() << '\n';

    b.set("foo");
    cout << b.ref<char *>() << '\n';

    b.set(string("bar"));
    cout << b.ref<string>() << '\n';
    return 0;
}

输出:

3
3
8
3
4
foo
bar

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-20
    相关资源
    最近更新 更多