【问题标题】:Pass pointer to temporary in c++ 11?在c ++ 11中将指针传递给临时?
【发布时间】:2017-11-23 14:51:06
【问题描述】:

我有一个现有的功能:

void foo(const Key* key = nullptr)
{
  // uses the key
}

我想将它的指针传递给临时 Key 对象(即右值),例如:

foo(&Key());

这会导致编译错误,但是在 c++ 11/14 中有没有办法我可以做到这一点?当然可以:

Key key;
foo(&key);

但是我不需要object Key,我只需要在foo()和foo()里面

或者我可以这样做:

foo(new Key());

但随后对象不会被删除。

【问题讨论】:

  • 添加void foo(const Key &key){ foo(&key); }怎么样?
  • 您是否同时使用了c++ 的三种不同标准?
  • @Detonar 我认为没有必要在其中引入动态分配。
  • @AndreyRubliov:因为它不是自然的,也不应该是自然的。
  • @AndreyRubliov:不自然的是将指针传递给临时对象。

标签: c++ c++11 c++14 c++17 temporary-objects


【解决方案1】:

我认为这不是一个好主意,但如果你真的想要一个临时的并且无法更改 foo,你可以将临时转换为 const&

int main()
{
    foo(&static_cast<const Key&>(Key{}));
}

live example on wandbox


或者,您可以将对象的创建“隐藏”在一个方便的函数后面:

template <typename T, typename F, typename... Ts>
void invoke_with_temporary(F&& f, Ts&&... xs)
{
    T obj{std::forward<Ts>(xs)...};
    f(&obj);
}

int main()
{
    invoke_with_temporary<Key>(&foo);
}

live example on wandbox.org


另一种选择:提供一个带有引用的foo 的重载:

void foo(Key&& key)
{
    foo(&key);
}

【讨论】:

  • 会导致未定义的行为吗?
  • @AndreyRubliov 临时变量将一直存在到表达式结束,这将在foo 返回之后。它的地址将在整个foo 中保持有效。未定义的行为是存储该指针并在临时不再存在后使用它。
  • @DavidHaim 我猜讨厌是主观的。但是没有模棱两可或正在玩“记忆游戏”。我可以同意,重载将是解决 OP 问题的更好解决方案,并且可以通过提出它来改进这个答案。但这篇文章确实回答了被问到的问题。
  • @DavidHaim:从字面上看,我回答中的第一句话是 “我认为这不是一个好主意,但如果你真的想要一个临时的并且无法更改 foo,你可以转换const&"的临时变量"...
  • @DavidHaim:我不同意——这不是在回答问题。 OP的问题很清楚,并且有我提供的答案。这是否是一个好主意是无关紧要的。在答案中提及这一点显然是一种好习惯。
【解决方案2】:

只需自己控制丢弃变量的范围:

{
    Key key;
    foo(&key);
} // <-- 'key' is destroyed here

【讨论】:

    【解决方案3】:

    这是一个实用功能。基本上是std::move1的倒数:

    template<class T>
    T& as_lvalue( T&& t ) { return t; }
    

    如果使用不当,可能会导致引用悬空。

    然后你的代码变成:

    foo(&as_lvalue(Key()));
    

    “不能获取临时地址”的目标是因为您可能会因为隐式临时创建等原因而获得极其意外的行为。

    在这种情况下,我们明确地采用临时地址。

    这并不比创建命名值、调用函数然后立即丢弃命名值更危险。


    1std::move 接受一个 l 或 r 值并返回一个对它的右值引用,表明消费者应该把它当作一个临时的,它的存在很快就会被丢弃。 as_lvalue 接受一个 l 或 r 值引用并返回一个对它的左值引用,表明消费者应该将其视为非临时的,其存在将持续存在。

    它们都是有效的操作,但std::move 更为关键。 (std::move 真的可以称为as_rvalue)。我建议不要使用像 unmove 这样的聪明名字。

    【讨论】:

    • 我喜欢这个,但我想知道制作它是否有什么要说的const T&amp; as_lvalue(const T&amp;&amp; t)?那不会用非右值编译,但是如果你已经有一个左值,为什么还要使用它?它还更清楚地表明您不应该在非常量上下文中使用它。还是我错过了什么?
    • @mattnewport 我认为在非常量上下文中使用它没有任何问题。结果被丢弃,但没有出错。有时您真的只想拥有一个可写的值,并且可以丢弃而无需创建命名变量。 std::move 适用于右值和 const 左值,as_lvalue 适用于左值,无论是否为 const,以及右值,无论是否为 const。
    • 我同意它没有任何问题,我只是不确定它在实践中有多有用。我猜一个函数需要一个你不感兴趣的非可选输出参数?我对这类事情的用例主要是通过 const 指针获取非可选输入参数的函数。
    • @mattnewport 一个函数通过指针获取一个可选的 out 参数,如果它存在与否,它会做不同的事情,所以你想将它传入。或者它通过指针获取一个非可选的 out 参数,你想忽略它。或者它通过引用做同样的事情。或者您正在与一个在 in 参数中采用非常量指针的遗留 API 交谈。此外,它适用于可选的参数。所有这些案件都将在时间中丢失,就像雨中的眼泪。
    • 好的,你提出了一个令人信服的案例 :) 我想我将来会使用 &amp;as_lvalue() 而不是我的答案中描述的 temp_ptr() 方法。
    【解决方案4】:

    采用指针而不是引用的唯一原因是指针是否可以为空。如果是这种情况,那么您的 foo 函数将如下所示:

    void foo(const Key *key)
    {
      if(key)
        //Do stuff with `key`
      else
        //Alternate code
    }
    

    鉴于此,您想要的是第二个重载,它将所有“用键做事”重构为它自己的函数,该函数需要一个引用。那就这样做吧:

    void foo(const Key &key)
    {
      //Do stuff with `key`
    }
    
    void foo(const Key *key)
    {
      if(key)
        foo(*key);
      else
        //Alternate code.
    }
    

    如果您有在这两种情况下都执行的通用代码,也可以将其重构为自己的函数。

    是的,“用钥匙做事”可能很复杂,并且分为几行。但这表明这里有一个非常奇怪的设计。

    你也可以用其他方式重构它:

    void foo(const Key &key) {foo(&key);}
    

    【讨论】:

    • 我同意这是糟糕的 API 设计,但我们中的一些人仍然需要使用许多现有的遗留 API,即使对于非可选参数也是如此。例如,请参阅我的答案中的 DirectX 11 示例。为所有这些 API 编写包装器并不总是可行的。
    • @mattnewport:但是您的 D3D 示例导致代码极其丑陋。如果一个函数调用语句需要 10+ 行,那么就大错特错了。基本上,大多数 D3D 结构不应该作为临时对象传递;他们的成员太多了。
    • 不幸的是,D3D API 设计适合丑陋的代码,但我仍然必须使用它。许多 D3D '参数结构' 具有适用于许多情况的合理默认值(这就是为什么它们提供方便的 CD3D11_XXX 辅助类),或者它们具有代表常见用途的特定(参数化)组合,如索引缓冲区。如果我想编写一个帮助函数来返回那些我仍然需要temp_ptr()
    【解决方案5】:

    我使用这样的东西:

    template <typename T>
    const T* temp_ptr(const T&& x) { return &x; }
    

    这样使用:

    foo(temp_ptr(Key{}));
    

    这在处理某些遗留 API 时非常有用。特别是 DirectX 11 经常采用 const T* 的参数聚合结构,并且内联创建和传递它们很方便。与这里的一些评论者不同,我认为这个习语没有任何问题,尽管我更喜欢这些 API 只是采用 const 引用并以不同方式处理可选参数。

    这是一个非常有用的示例 D3D11 API 调用:

        vector<Vec3f> verts;
        ...
        ID3D11BufferPtr vbuf;
        d3dDevice->CreateBuffer(
            temp_ptr(CD3D11_BUFFER_DESC{byteSize(verts), D3D11_BIND_VERTEX_BUFFER}),
            temp_ptr(D3D11_SUBRESOURCE_DATA{data(verts)}), &vbuf);
    

    用于调用ID3D11Device::CreateBuffer()创建顶点缓冲区。

    在较大的项目中,我可能会为许多 D3D API 调用编写包装器,这使得它们更方便地以现代 C++ 风格调用,但对于我希望拥有最少额外代码或依赖项的小型独立示例项目,我发现这非常有用.

    我过去使用的另一个有效技巧是:

    foo(std::data({Key{}}));
    

    但我并不特别推荐这样做,因为我认为其意图不明确并且依赖于对初始化列表如何工作的太多了解。如果您需要传递一个临时的“数组”,则变体很有用:

    d3dDeviceContext->ClearRenderTargetView(rendertarget, data({0.0f, 0.0f, 0.0f, 0.0f}));
    

    用于调用 ID3D11DeviceContext::ClearRenderTargetView() 之类的 API。

    【讨论】:

      【解决方案6】:

      如果您关心的是变量范围(正如您在评论中提到的),您可以使用

      {Key key;foo(&amp;key);}

      【讨论】:

        【解决方案7】:

        或者我可以这样做:

        foo(new Key());
        

        但随后对象不会被删除。

        您可以创建一个智能指针,但我不想这样做。不过还是有可能的。

        #include <memory>
        foo(std::make_unique<const Key>().get());
        

        【讨论】:

        • @Veedrac 没有内存泄漏。临时智能指针将一直存在,直到对完整的表达式求值,然后分配给创建的 const Key 的内存将被释放。
        • 呸,把我的方法搞混了。不过,这里的堆分配很愚蠢。
        • @Veedrac 我就是这么说的。
        • 那么为什么会有这个答案呢?
        猜你喜欢
        • 2018-04-08
        • 2018-11-24
        • 1970-01-01
        • 2018-08-11
        • 2012-02-05
        • 1970-01-01
        • 1970-01-01
        • 2012-01-05
        • 2012-02-22
        相关资源
        最近更新 更多