使用C ++ 11智能指针作为C函数参数

时间:2014-12-28 21:56:48

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

虽然这应该是一个微不足道的问题,但到目前为止我找不到答案。在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 **)'                                         musicStream.get());

                                                   ^

3 个答案:

答案 0 :(得分:4)

您的问题是因为C API希望您将地址传递给FMOD_SYSTEM指针,以便API可以用结果填充该指针 - 即,它需要FMOD_SYSTEM*作为 out-parameter

在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作为out参数传递,然后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));

答案是要认识到问题的根源是out-parameters本身,并且解决方案是值语义。我们想要编写的惯用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;

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

答案 1 :(得分:3)

总的来说,我不相信将智能指针与C-API混合是一个好主意,更好的方法是将C-API包装在C ++类中,如Quuxplusone建议的那样。

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

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);

答案 2 :(得分:2)

简单的答案是:C ++智能指针用于您使用newdelete创建的自我分配对象。 它们不是为指向对象的指针构建的,这些对象是由第三方C风格API函数内部创建的,需要由另一个C Style API函数发布。 在您的情况下,您必须致电System::release而不是delete,因此使用智能指针可能会导致严重问题。

您有三种可能性:

  1. 只需坚持使用C Style并自行处理释放。 (我更喜欢这个)
  2. 编写自己的C ++包装类 - 如Quuxplusone建议的那样
  3. 使用自定义“分配器”修改C ++智能指针 “删除”(这很复杂,理论上,不推荐)