将 unique_ptr<T> 通过 reinterpret_cast 强转为 T ** 的可行性

项目里用的都是现代 C++, 原则上不出现裸指针, 用的都是 STL 的智能指针

但是用到的库里面, 有个函数要求给一个 T ** 的, 而 unique_ptr 本身没有提供返回封装的指针的引用, 所以我实在想不到什么办法获得 unique_ptr 内部的指针然后取其引用传给这个需要 T ** 参数的函数

库是用 dll 封装好的, 只能看到它的申明, 看不到它内部的实现

后来我想到了用 reinterpret_cast

但是对底层不太了解, 所以实在是不太敢用

我写过一个 demo, 好像这种方式是可行的

#include <iostream>

using namespace std;
struct s {
    string a;
    string b;
    string c;
};
void func(s **obj) {
    cout << (*obj)->a << endl;
    cout << (*obj)->b << endl;
    cout << (*obj)->c << endl;
}
int main(int argc, char *argv[]) {
    unique_ptr<s> p {new s({"123", "sdfadsf", "43g43g4g"})};
    func(reinterpret_cast<s **>(&p));
}

因为对标准和底层实现不了解, 所以不太敢用, 需要各位大神的指点

如果这种不可行的话, 是不是除了改成原始指针和自己造轮子就没有其他方法了 (那种算偏移量的邪门方法就算了...)

阅读 4.3k
3 个回答

这样用reinterpret_cast会导致UB。即便放在一个特定的环境下,强行用也会使得兼容性,移植性等等都得不到保障。
在转换之前可以进行一些检查,如:

static_assert(
  sizeof(int *) == sizeof(std::unique_ptr<int>) &&
  std::is_standard_layout_v<std::unique_ptr<int>>, "");

在这里用reinterpret_cast不是一个合法的做法。


合法的做法:

因为func的参数是二级指针,实际上你需要先释放unique_ptr的所有权,然后调用,最后再将临时指针设回unique_ptr,即:

auto *raw = p.release();
func(&raw);
p.reset(raw);

否则func对raw的修改可能被忽略,进而引发诸如内存泄漏等问题。

这三个步骤可以分装成类,让调用更简洁,也提供更强的异常安全性:

template <class T>
class TransferHelper {
public:
    TransferHelper(std::unique_ptr<T> &ptr) noexcept : unique_(&ptr), temp_(ptr.release()) {}
    TransferHelper(const TransferHelper &) = delete;
    TransferHelper(TransferHelper &&rhs) noexcept : unique_(std::exchange(rhs.unique_, nullptr)) {}

    ~TransferHelper() noexcept {
        if (unique_)
            unique_->reset(temp_);
    }

    TransferHelper &operator=(const TransferHelper &) = delete;

    operator T **() && noexcept {
        return &temp_;
    }

protected:
    TransferHelper() = default;
    void Set(std::unique_ptr<T> &ptr) noexcept {
        unique_ = &ptr;
        temp_ = unique_->release();
    }

private:
    std::unique_ptr<T> *unique_ = nullptr;
    T *temp_ = nullptr;
};

template <class T>
class TransferHelperHolder : private TransferHelper<T> {
public:
    TransferHelperHolder(std::unique_ptr<T> &&ptr) noexcept : owned_unique_(std::move(ptr)) {
        TransferHelper<T>::Set(owned_unique_);
    }

    using TransferHelper<T>::operator T **;

private:
    std::unique_ptr<T> owned_unique_;
};

template <class T>
TransferHelper<T> Transfer(std::unique_ptr<T> &ptr) {
    return ptr;
}

template <class T>
TransferHelperHolder<T> Transfer(std::unique_ptr<T> &&ptr) {
    return std::move(ptr);
}

int main(int argc, char *argv[]) {
    std::unique_ptr<s> p = std::make_unique<s>(s{"123", "sdfadsf", "43g43g4g" });

    func(Transfer(p));

    return 0;
}

PS:以上代码没有经过严格测试,仅供参考。实际使用前建议编写一份完整的测试样例。

我建议找库作者或者自己测试一下这个函数,送指针的指针,一般是用来初始化对象的,错误的用法可能会导致内存泄露.

比如说

int foo_init(foo** fp) 
{
  if (fp == nullptr)
    return -1;
  if (*fp == nullptr) {
    *fp = malloc(sizeof(foo));
    if (*fp == nullptr)
      return -1;
  }
  return foo_init_internal(*fp);
}

//使用方法1,由foo_init构造对象,foo_release析构,foo_free释放内存
foo* fp = nullptr;
foo_init(&fp);
foo_release(fp);
foo_free(&fp);
//使用方法2,有结构体声明,直接栈上内存,foo_init忽略创建内存,直接构造对象,foo_release析构对象,不需要释放内存
foo f;
foo* fp = &f;
foo_init(&fp);
foo_release(fp);
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题