【问题标题】:Using C++11 smart pointers as C function arguments使用 C++11 智能指针作为 C 函数参数
【发布时间】:2015-02-25 04:10:11
【问题描述】:

虽然这应该是一个微不足道的问题,但到目前为止我无法找到答案。在 C API 中,有许多函数将指针和指向指针的指针作为参数。 如何使用正确的智能指针作为 C API 的参数。

这是一个我想转换为使用 std::unique_ptr 的示例:

FMOD_SYSTEM* system = nullptr;
result = FMOD_System_Create(&system); // Create the main system object

FMOD_SOUND* musicStream;
result = FMOD_System_CreateSound(system,
                                 musicStreamPath,
                                 FMOD_CREATESTREAM,
                                 nullptr,
                                 &musicStream);

参考: FMOD_System_Create FMOD_System_CreateSound

我开始将智能指针声明为:

std::unique_ptr<FMOD_SYSTEM> system = nullptr;
std::unique_ptr<FMOD_SOUND> musicStream = nullptr;

如果我使用 .get(),以下是编译器错误:

无法转换 'std::unique_ptr::pointer {aka FMOD_SOUND*}' 到 'FMOD_SOUND**' 用于参数 '5' 到 'FMOD_RESULT FMOD_System_CreateSound(FMOD_SYSTEM*, const char*, FMOD_MODE, FMOD_CREATESOUNDEXINFO*, FMOD_SOUND**)' 音乐流.get());

                                                   ^

【问题讨论】:

  • 使用.get()方法获取底层指针?
  • 不能只使用 .get()。似乎还有更多,但我还想不通。
  • 这里有一个真正的问题和答案。我正在努力。
  • 无论如何,除非您实际上应该在该指针上调用 delete(如果它由 C API 返回,则几乎不可能),您必须提供自定义删除器。

标签: c pointers c++11 smart-pointers


【解决方案1】:

您的问题是因为 C API 希望您向它传递 地址 指向 FMOD_SYSTEM 的指针,以便 API 可以用结果填充该指针 - 即,它需要FMOD_SYSTEM* 作为输出参数。

在 C++ 中,这样做的惯用方法是将 引用一个(智能?)指针传递给 FMOD_SYSTEM,即 C API 所在的位置

FMOD_RESULT FMOD_System_Create(FMOD_SYSTEM **system);

FMOD_SYSTEM *system;
result = FMOD_System_Create(&system);

C++ API 是

FMOD_RESULT FMOD_System_Create(std::unique_ptr<FMOD_SYSTEM> &system);

std::unique_ptr<FMOD_SYSTEM> system;
result = FMOD_System_Create(system);

但是,这个 C++ API 有一个大问题!问题在于创建 FMOD_SYSTEM 和将其包装在unique_ptr 中是独立的问题,不应该像这样混在一起。例如,如果我正在使用线程做一些聪明的事情并且真的需要我的FMOD_SYSTEM 由shared_ptr 而不是简单的unique_ptr 管理怎么办?我必须创建一个unique_ptr 作为输出参数传递,然后将std::move 传递给shared_ptr 吗?这既丑陋又(微)低效。

std::unique_ptr<FMOD_SYSTEM> fake_system;
result = FMOD_System_Create(fake_system);
std::shared_ptr<FMOD_SYSTEM> system(std::move(fake_system));

答案是要认识到问题的根源在于参数本身,而解决方案是值语义。我们想要编写的惯用 C++ 语法是

auto system = std::make_unique<fmod_system>();

我们获得该语法的方式是将 C API 的原始指针封装在值类中:

class fmod_system {
    FMOD_SYSTEM *ptr;
    fmod_system() {
        auto result = FMOD_System_Create(&ptr);
        if (result != FMOD_OK) {
            ptr = nullptr;
            throw something;
        }
    }
    fmod_system(fmod_system&&) = default;
    fmod_system& operator=(fmod_system&&) = default;
    fmod_system(const fmod_system&) = delete;
    fmod_system& operator=(const fmod_system&) = delete;
    ~fmod_system() {
        auto result = FMOD_System_Release(ptr);
        assert(result == FMOD_OK);  // destructors shouldn't throw: use your best judgment here
    }
};

事实上,此时我们的调用者可以放弃unique_ptr 混淆并简单地编写

fmod_system system;

除非他们真的需要出于某种原因额外的指针语义层。

【讨论】:

  • 在这种情况下。是否有如何以适当的 C++ 风格使用 C API 的一般经验法则?
  • @Zingam:现在的热门词是“价值语义”;较旧且几乎同义的短语是“RAII”。谷歌搜索这两个应该会出现一些好的经验法则。如果这没有帮助,我建议发布另一个问题 - 并使其尽可能集中。和/或,尝试自己编写代码,然后将其发布到CodeReview Stack Exchange 以获得反馈!
  • 虽然这两个术语都是很好的搜索提示,但值语义和 RAII 是两个主要正交的概念。值语义是关于(简化)使您的类型表现得像ints(特别是在复制语义方面)。 RAII 是一种确保资源自动释放的技术
  • @MikeMB 绝对正确;虽然后来我回去阅读了我的答案,但我想我仍然认为“值语义和 RAII [相似]”是真的在这个特定的上下文中。 RAII 是关于编写好的析构函数;值语义通常是关于编写好的特殊成员函数;两者都是关于如何避免像 Zingam 最初的做法那样将 shared_ptr/raw-pointer 滑稽动作暴露给最终用户。 (即:在这种情况下我会坚持我的断言,即它们“几乎是同义词”而不是“大部分正交”。我认为我们都同意这不是 100% 或 0 %.)
【解决方案2】:

一般来说,我认为将 C-API 与智能指针混合不是一个好主意,更好的方法是按照 Quuxplusone 的建议将 C-API 包装在 C++ 类中。

但是,要回答您的问题,我能想到的“最干净”(但仍然丑陋的方式)是在创建对象后将原始指针传递给带有自定义删除器的智能指针。

struct FMOD_SYSTEM_Deleter {    
    void operator()(FMOD_SYSTEM* sys) {
         if (sys !=  nullptr) {
             FMOD_System_Release(sys);
         }
    }
}

FMOD_SYSTEM* tsys = nullptr;
result = FMOD_System_Create(&tsys);
std::unique_ptr<FMOD_SYSTEM,FMOD_SYSTEM_Deleter> system(tsys);

【讨论】:

    【解决方案3】:

    简单的答案是:C++ 智能指针用于您将使用new 和delete 创建的自分配对象。 它们不是为指向对象的指针而构建的,它们是由第 3 方 C 样式 API 函数在内部创建的,需要由另一个 C 样式 API 函数释放。 在您的情况下,您必须调用 System::release 而不是 delete,因此使用智能指针可能会导致大问题。

    你有三种可能:

    1. 只需保持 C 风格,自己负责调用 release。 (我更喜欢这个)
    2. 编写您自己的 C++ 包装类 - 就像 Quuxplusone 建议的那样
    3. 使用自定义“分配器”修改 C++ 智能指针和 “deleter”(这很复杂,理论上不推荐)

    【讨论】:

    • 没有关于使用 c++ smpartpointer 和 costum 删除器的理论(而且可以说它也不太复杂)。
    猜你喜欢
    • 2014-11-22
    • 2015-10-30
    • 2012-01-10
    • 1970-01-01
    • 1970-01-01
    • 2016-09-17
    • 2017-11-28
    • 1970-01-01
    • 2014-03-10
    相关资源
    最近更新 更多